chore(deps): bump github.com/go-git/go-git/v5 from 5.7.0 to 5.8.0 (#2633)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.7.0 to 5.8.0. - [Release notes](https://github.com/go-git/go-git/releases) - [Commits](https://github.com/go-git/go-git/compare/v5.7.0...v5.8.0) --- updated-dependencies: - dependency-name: github.com/go-git/go-git/v5 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
b33b7c432c
commit
b314518f0d
2
go.mod
2
go.mod
|
|
@ -19,7 +19,7 @@ require (
|
||||||
github.com/containerd/cgroups v1.1.0 // indirect
|
github.com/containerd/cgroups v1.1.0 // indirect
|
||||||
github.com/docker/docker v23.0.5+incompatible
|
github.com/docker/docker v23.0.5+incompatible
|
||||||
github.com/go-git/go-billy/v5 v5.4.1
|
github.com/go-git/go-billy/v5 v5.4.1
|
||||||
github.com/go-git/go-git/v5 v5.7.0
|
github.com/go-git/go-git/v5 v5.8.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/google/go-cmp v0.5.9
|
github.com/google/go-cmp v0.5.9
|
||||||
github.com/google/go-containerregistry v0.15.2
|
github.com/google/go-containerregistry v0.15.2
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -271,8 +271,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS
|
||||||
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
||||||
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
||||||
github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE=
|
github.com/go-git/go-git/v5 v5.8.0 h1:Rc543s6Tyq+YcyPwZRvU4jzZGM8rB/wWu94TnTIYALQ=
|
||||||
github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
|
github.com/go-git/go-git/v5 v5.8.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# go-git Security Policy
|
||||||
|
|
||||||
|
The purpose of this security policy is to outline `go-git`'s process
|
||||||
|
for reporting, handling and disclosing security sensitive information.
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
The project follows a version support policy where only the latest minor
|
||||||
|
release is actively supported. Therefore, only issues that impact the latest
|
||||||
|
minor release will be fixed. Users are encouraged to upgrade to the latest
|
||||||
|
minor/patch release to benefit from the most up-to-date features, bug fixes,
|
||||||
|
and security enhancements.
|
||||||
|
|
||||||
|
The supported versions policy applies to both the `go-git` library and its
|
||||||
|
associated repositories within the `go-git` org.
|
||||||
|
|
||||||
|
## Reporting Security Issues
|
||||||
|
|
||||||
|
Please report any security vulnerabilities or potential weaknesses in `go-git`
|
||||||
|
privately via go-git-security@googlegroups.com. Do not publicly disclose the
|
||||||
|
details of the vulnerability until a fix has been implemented and released.
|
||||||
|
|
||||||
|
During the process the project maintainers will investigate the report, so please
|
||||||
|
provide detailed information, including steps to reproduce, affected versions, and any mitigations if known.
|
||||||
|
|
||||||
|
The project maintainers will acknowledge the receipt of the report and work with
|
||||||
|
the reporter to validate and address the issue.
|
||||||
|
|
||||||
|
Please note that `go-git` does not have any bounty programs, and therefore do
|
||||||
|
not provide financial compensation for disclosures.
|
||||||
|
|
||||||
|
## Security Disclosure Process
|
||||||
|
|
||||||
|
The project maintainers will make every effort to promptly address security issues.
|
||||||
|
|
||||||
|
Once a security vulnerability is fixed, a security advisory will be published to notify users and provide appropriate mitigation measures.
|
||||||
|
|
||||||
|
All `go-git` advisories can be found at https://github.com/go-git/go-git/security/advisories.
|
||||||
|
|
@ -2,16 +2,18 @@ package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"container/heap"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
"github.com/go-git/go-git/v5/utils/diff"
|
"github.com/go-git/go-git/v5/utils/diff"
|
||||||
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BlameResult represents the result of a Blame operation.
|
// BlameResult represents the result of a Blame operation.
|
||||||
|
|
@ -29,53 +31,26 @@ type BlameResult struct {
|
||||||
func Blame(c *object.Commit, path string) (*BlameResult, error) {
|
func Blame(c *object.Commit, path string) (*BlameResult, error) {
|
||||||
// The file to blame is identified by the input arguments:
|
// The file to blame is identified by the input arguments:
|
||||||
// commit and path. commit is a Commit object obtained from a Repository. Path
|
// commit and path. commit is a Commit object obtained from a Repository. Path
|
||||||
// represents a path to a specific file contained into the repository.
|
// represents a path to a specific file contained in the repository.
|
||||||
//
|
//
|
||||||
// Blaming a file is a two step process:
|
// Blaming a file is done by walking the tree in reverse order trying to find where each line was last modified.
|
||||||
//
|
//
|
||||||
// 1. Create a linear history of the commits affecting a file. We use
|
// When a diff is found it cannot immediately assume it came from that commit, as it may have come from 1 of its
|
||||||
// revlist.New for that.
|
// parents, so it will first try to resolve those diffs from its parents, if it couldn't find the change in its
|
||||||
|
// parents then it will assign the change to itself.
|
||||||
//
|
//
|
||||||
// 2. Then build a graph with a node for every line in every file in
|
// When encountering 2 parents that have made the same change to a file it will choose the parent that was merged
|
||||||
// the history of the file.
|
// into the current branch first (this is determined by the order of the parents inside the commit).
|
||||||
//
|
//
|
||||||
// Each node is assigned a commit: Start by the nodes in the first
|
// This currently works on a line by line basis, if performance becomes an issue it could be changed to work with
|
||||||
// commit. Assign that commit as the creator of all its lines.
|
// hunks rather than lines. Then when encountering diff hunks it would need to split them where necessary.
|
||||||
//
|
|
||||||
// Then jump to the nodes in the next commit, and calculate the diff
|
|
||||||
// between the two files. Newly created lines get
|
|
||||||
// assigned the new commit as its origin. Modified lines also get
|
|
||||||
// this new commit. Untouched lines retain the old commit.
|
|
||||||
//
|
|
||||||
// All this work is done in the assignOrigin function which holds all
|
|
||||||
// the internal relevant data in a "blame" struct, that is not
|
|
||||||
// exported.
|
|
||||||
//
|
|
||||||
// TODO: ways to improve the efficiency of this function:
|
|
||||||
// 1. Improve revlist
|
|
||||||
// 2. Improve how to traverse the history (example a backward traversal will
|
|
||||||
// be much more efficient)
|
|
||||||
//
|
|
||||||
// TODO: ways to improve the function in general:
|
|
||||||
// 1. Add memoization between revlist and assign.
|
|
||||||
// 2. It is using much more memory than needed, see the TODOs below.
|
|
||||||
|
|
||||||
b := new(blame)
|
b := new(blame)
|
||||||
b.fRev = c
|
b.fRev = c
|
||||||
b.path = path
|
b.path = path
|
||||||
|
b.q = new(priorityQueue)
|
||||||
|
|
||||||
// get all the file revisions
|
file, err := b.fRev.File(path)
|
||||||
if err := b.fillRevs(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate the line tracking graph and fill in
|
|
||||||
// file contents in data.
|
|
||||||
if err := b.fillGraphAndData(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := b.fRev.File(b.path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -83,13 +58,59 @@ func Blame(c *object.Commit, path string) (*BlameResult, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
finalLength := len(finalLines)
|
||||||
|
|
||||||
// Each node (line) holds the commit where it was introduced or
|
needsMap := make([]lineMap, finalLength)
|
||||||
// last modified. To achieve that we use the FORWARD algorithm
|
for i := range needsMap {
|
||||||
// described in Zimmermann, et al. "Mining Version Archives for
|
needsMap[i] = lineMap{i, i, nil, -1}
|
||||||
// Co-changed Lines", in proceedings of the Mining Software
|
}
|
||||||
// Repositories workshop, Shanghai, May 22-23, 2006.
|
contents, err := file.Contents()
|
||||||
lines, err := newLines(finalLines, b.sliceGraph(len(b.graph)-1))
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.q.Push(&queueItem{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
c,
|
||||||
|
path,
|
||||||
|
contents,
|
||||||
|
needsMap,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
})
|
||||||
|
items := make([]*queueItem, 0)
|
||||||
|
for {
|
||||||
|
items = items[:0]
|
||||||
|
for {
|
||||||
|
if b.q.Len() == 0 {
|
||||||
|
return nil, errors.New("invalid state: no items left on the blame queue")
|
||||||
|
}
|
||||||
|
item := b.q.Pop()
|
||||||
|
items = append(items, item)
|
||||||
|
next := b.q.Peek()
|
||||||
|
if next == nil || next.Hash != item.Commit.Hash {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finished, err := b.addBlames(items)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if finished == true {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.lineToCommit = make([]*object.Commit, finalLength)
|
||||||
|
for i := range needsMap {
|
||||||
|
b.lineToCommit[i] = needsMap[i].Commit
|
||||||
|
}
|
||||||
|
|
||||||
|
lines, err := newLines(finalLines, b.lineToCommit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -105,6 +126,8 @@ func Blame(c *object.Commit, path string) (*BlameResult, error) {
|
||||||
type Line struct {
|
type Line struct {
|
||||||
// Author is the email address of the last author that modified the line.
|
// Author is the email address of the last author that modified the line.
|
||||||
Author string
|
Author string
|
||||||
|
// AuthorName is the name of the last author that modified the line.
|
||||||
|
AuthorName string
|
||||||
// Text is the original text of the line.
|
// Text is the original text of the line.
|
||||||
Text string
|
Text string
|
||||||
// Date is when the original text of the line was introduced
|
// Date is when the original text of the line was introduced
|
||||||
|
|
@ -113,9 +136,10 @@ type Line struct {
|
||||||
Hash plumbing.Hash
|
Hash plumbing.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLine(author, text string, date time.Time, hash plumbing.Hash) *Line {
|
func newLine(author, authorName, text string, date time.Time, hash plumbing.Hash) *Line {
|
||||||
return &Line{
|
return &Line{
|
||||||
Author: author,
|
Author: author,
|
||||||
|
AuthorName: authorName,
|
||||||
Text: text,
|
Text: text,
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
Date: date,
|
Date: date,
|
||||||
|
|
@ -123,21 +147,10 @@ func newLine(author, text string, date time.Time, hash plumbing.Hash) *Line {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLines(contents []string, commits []*object.Commit) ([]*Line, error) {
|
func newLines(contents []string, commits []*object.Commit) ([]*Line, error) {
|
||||||
lcontents := len(contents)
|
result := make([]*Line, 0, len(contents))
|
||||||
lcommits := len(commits)
|
|
||||||
|
|
||||||
if lcontents != lcommits {
|
|
||||||
if lcontents == lcommits-1 && contents[lcontents-1] != "\n" {
|
|
||||||
contents = append(contents, "\n")
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("contents and commits have different length")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]*Line, 0, lcontents)
|
|
||||||
for i := range contents {
|
for i := range contents {
|
||||||
result = append(result, newLine(
|
result = append(result, newLine(
|
||||||
commits[i].Author.Email, contents[i],
|
commits[i].Author.Email, commits[i].Author.Name, contents[i],
|
||||||
commits[i].Author.When, commits[i].Hash,
|
commits[i].Author.When, commits[i].Hash,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
@ -152,151 +165,426 @@ type blame struct {
|
||||||
path string
|
path string
|
||||||
// the commit of the final revision of the file to blame
|
// the commit of the final revision of the file to blame
|
||||||
fRev *object.Commit
|
fRev *object.Commit
|
||||||
// the chain of revisions affecting the the file to blame
|
// resolved lines
|
||||||
revs []*object.Commit
|
lineToCommit []*object.Commit
|
||||||
// the contents of the file across all its revisions
|
// queue of commits that need resolving
|
||||||
data []string
|
q *priorityQueue
|
||||||
// the graph of the lines in the file across all the revisions
|
|
||||||
graph [][]*object.Commit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate the history of a file "path", starting from commit "from", sorted by commit date.
|
type lineMap struct {
|
||||||
func (b *blame) fillRevs() error {
|
Orig, Cur int
|
||||||
var err error
|
Commit *object.Commit
|
||||||
|
FromParentNo int
|
||||||
b.revs, err = references(b.fRev, b.path)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// build graph of a file from its revision history
|
func (b *blame) addBlames(curItems []*queueItem) (bool, error) {
|
||||||
func (b *blame) fillGraphAndData() error {
|
curItem := curItems[0]
|
||||||
//TODO: not all commits are needed, only the current rev and the prev
|
|
||||||
b.graph = make([][]*object.Commit, len(b.revs))
|
// Simple optimisation to merge paths, there is potential to go a bit further here and check for any duplicates
|
||||||
b.data = make([]string, len(b.revs)) // file contents in all the revisions
|
// not only if they are all the same.
|
||||||
// for every revision of the file, starting with the first
|
if len(curItems) == 1 {
|
||||||
// one...
|
curItems = nil
|
||||||
for i, rev := range b.revs {
|
} else if curItem.IdenticalToChild {
|
||||||
// get the contents of the file
|
allSame := true
|
||||||
file, err := rev.File(b.path)
|
lenCurItems := len(curItems)
|
||||||
if err != nil {
|
lowestParentNo := curItem.ParentNo
|
||||||
return nil
|
for i := 1; i < lenCurItems; i++ {
|
||||||
|
if !curItems[i].IdenticalToChild || curItem.Child != curItems[i].Child {
|
||||||
|
allSame = false
|
||||||
|
break
|
||||||
}
|
}
|
||||||
b.data[i], err = file.Contents()
|
lowestParentNo = min(lowestParentNo, curItems[i].ParentNo)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
nLines := countLines(b.data[i])
|
if allSame {
|
||||||
// create a node for each line
|
curItem.Child.numParentsNeedResolving = curItem.Child.numParentsNeedResolving - lenCurItems + 1
|
||||||
b.graph[i] = make([]*object.Commit, nLines)
|
curItems = nil // free the memory
|
||||||
// assign a commit to each node
|
curItem.ParentNo = lowestParentNo
|
||||||
// if this is the first revision, then the node is assigned to
|
|
||||||
// this first commit.
|
// Now check if we can remove the parent completely
|
||||||
if i == 0 {
|
for curItem.Child.IdenticalToChild && curItem.Child.MergedChildren == nil && curItem.Child.numParentsNeedResolving == 1 {
|
||||||
for j := 0; j < nLines; j++ {
|
oldChild := curItem.Child
|
||||||
b.graph[i][j] = b.revs[i]
|
curItem.Child = oldChild.Child
|
||||||
|
curItem.ParentNo = oldChild.ParentNo
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have more than 1 item for this commit, create a single needsMap
|
||||||
|
if len(curItems) > 1 {
|
||||||
|
curItem.MergedChildren = make([]childToNeedsMap, len(curItems))
|
||||||
|
for i, c := range curItems {
|
||||||
|
curItem.MergedChildren[i] = childToNeedsMap{c.Child, c.NeedsMap, c.IdenticalToChild, c.ParentNo}
|
||||||
|
}
|
||||||
|
newNeedsMap := make([]lineMap, 0, len(curItem.NeedsMap))
|
||||||
|
newNeedsMap = append(newNeedsMap, curItems[0].NeedsMap...)
|
||||||
|
|
||||||
|
for i := 1; i < len(curItems); i++ {
|
||||||
|
cur := curItems[i].NeedsMap
|
||||||
|
n := 0 // position in newNeedsMap
|
||||||
|
c := 0 // position in current list
|
||||||
|
for c < len(cur) {
|
||||||
|
if n == len(newNeedsMap) {
|
||||||
|
newNeedsMap = append(newNeedsMap, cur[c:]...)
|
||||||
|
break
|
||||||
|
} else if newNeedsMap[n].Cur == cur[c].Cur {
|
||||||
|
n++
|
||||||
|
c++
|
||||||
|
} else if newNeedsMap[n].Cur < cur[c].Cur {
|
||||||
|
n++
|
||||||
} else {
|
} else {
|
||||||
// if this is not the first commit, then assign to the old
|
newNeedsMap = append(newNeedsMap, cur[c])
|
||||||
// commit or to the new one, depending on what the diff
|
newPos := len(newNeedsMap) - 1
|
||||||
// says.
|
for newPos > n {
|
||||||
b.assignOrigin(i, i-1)
|
newNeedsMap[newPos-1], newNeedsMap[newPos] = newNeedsMap[newPos], newNeedsMap[newPos-1]
|
||||||
|
newPos--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
|
}
|
||||||
|
curItem.NeedsMap = newNeedsMap
|
||||||
|
curItem.IdenticalToChild = false
|
||||||
|
curItem.Child = nil
|
||||||
|
curItems = nil // free the memory
|
||||||
}
|
}
|
||||||
|
|
||||||
// sliceGraph returns a slice of commits (one per line) for a particular
|
parents, err := parentsContainingPath(curItem.path, curItem.Commit)
|
||||||
// revision of a file (0=first revision).
|
if err != nil {
|
||||||
func (b *blame) sliceGraph(i int) []*object.Commit {
|
return false, err
|
||||||
fVs := b.graph[i]
|
|
||||||
result := make([]*object.Commit, 0, len(fVs))
|
|
||||||
for _, v := range fVs {
|
|
||||||
c := *v
|
|
||||||
result = append(result, &c)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assigns origin to vertexes in current (c) rev from data in its previous (p)
|
anyPushed := false
|
||||||
// revision
|
for parnetNo, prev := range parents {
|
||||||
func (b *blame) assignOrigin(c, p int) {
|
currentHash, err := blobHash(curItem.path, curItem.Commit)
|
||||||
// assign origin based on diff info
|
if err != nil {
|
||||||
hunks := diff.Do(b.data[p], b.data[c])
|
return false, err
|
||||||
sl := -1 // source line
|
}
|
||||||
dl := -1 // destination line
|
prevHash, err := blobHash(prev.Path, prev.Commit)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if currentHash == prevHash {
|
||||||
|
if len(parents) == 1 && curItem.MergedChildren == nil && curItem.IdenticalToChild {
|
||||||
|
// commit that has 1 parent and 1 child and is the same as both, bypass it completely
|
||||||
|
b.q.Push(&queueItem{
|
||||||
|
Child: curItem.Child,
|
||||||
|
Commit: prev.Commit,
|
||||||
|
path: prev.Path,
|
||||||
|
Contents: curItem.Contents,
|
||||||
|
NeedsMap: curItem.NeedsMap, // reuse the NeedsMap as we are throwing away this item
|
||||||
|
IdenticalToChild: true,
|
||||||
|
ParentNo: curItem.ParentNo,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
b.q.Push(&queueItem{
|
||||||
|
Child: curItem,
|
||||||
|
Commit: prev.Commit,
|
||||||
|
path: prev.Path,
|
||||||
|
Contents: curItem.Contents,
|
||||||
|
NeedsMap: append([]lineMap(nil), curItem.NeedsMap...), // create new slice and copy
|
||||||
|
IdenticalToChild: true,
|
||||||
|
ParentNo: parnetNo,
|
||||||
|
})
|
||||||
|
curItem.numParentsNeedResolving++
|
||||||
|
}
|
||||||
|
anyPushed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the contents of the file
|
||||||
|
file, err := prev.Commit.File(prev.Path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
prevContents, err := file.Contents()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hunks := diff.Do(prevContents, curItem.Contents)
|
||||||
|
prevl := -1
|
||||||
|
curl := -1
|
||||||
|
need := 0
|
||||||
|
getFromParent := make([]lineMap, 0)
|
||||||
|
out:
|
||||||
for h := range hunks {
|
for h := range hunks {
|
||||||
hLines := countLines(hunks[h].Text)
|
hLines := countLines(hunks[h].Text)
|
||||||
for hl := 0; hl < hLines; hl++ {
|
for hl := 0; hl < hLines; hl++ {
|
||||||
switch {
|
switch {
|
||||||
case hunks[h].Type == 0:
|
case hunks[h].Type == diffmatchpatch.DiffEqual:
|
||||||
sl++
|
prevl++
|
||||||
dl++
|
curl++
|
||||||
b.graph[c][dl] = b.graph[p][sl]
|
if curl == curItem.NeedsMap[need].Cur {
|
||||||
case hunks[h].Type == 1:
|
// add to needs
|
||||||
dl++
|
getFromParent = append(getFromParent, lineMap{curl, prevl, nil, -1})
|
||||||
b.graph[c][dl] = b.revs[c]
|
// move to next need
|
||||||
case hunks[h].Type == -1:
|
need++
|
||||||
sl++
|
if need >= len(curItem.NeedsMap) {
|
||||||
default:
|
break out
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case hunks[h].Type == diffmatchpatch.DiffInsert:
|
||||||
|
curl++
|
||||||
|
if curl == curItem.NeedsMap[need].Cur {
|
||||||
|
// the line we want is added, it may have been added here (or by another parent), skip it for now
|
||||||
|
need++
|
||||||
|
if need >= len(curItem.NeedsMap) {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case hunks[h].Type == diffmatchpatch.DiffDelete:
|
||||||
|
prevl += hLines
|
||||||
|
continue out
|
||||||
|
default:
|
||||||
|
return false, errors.New("invalid state: invalid hunk Type")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoString prints the results of a Blame using git-blame's style.
|
if len(getFromParent) > 0 {
|
||||||
func (b *blame) GoString() string {
|
b.q.Push(&queueItem{
|
||||||
|
curItem,
|
||||||
|
nil,
|
||||||
|
prev.Commit,
|
||||||
|
prev.Path,
|
||||||
|
prevContents,
|
||||||
|
getFromParent,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
parnetNo,
|
||||||
|
})
|
||||||
|
curItem.numParentsNeedResolving++
|
||||||
|
anyPushed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curItem.Contents = "" // no longer need, free the memory
|
||||||
|
|
||||||
|
if !anyPushed {
|
||||||
|
return finishNeeds(curItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func finishNeeds(curItem *queueItem) (bool, error) {
|
||||||
|
// any needs left in the needsMap must have come from this revision
|
||||||
|
for i := range curItem.NeedsMap {
|
||||||
|
if curItem.NeedsMap[i].Commit == nil {
|
||||||
|
curItem.NeedsMap[i].Commit = curItem.Commit
|
||||||
|
curItem.NeedsMap[i].FromParentNo = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if curItem.Child == nil && curItem.MergedChildren == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if curItem.MergedChildren == nil {
|
||||||
|
return applyNeeds(curItem.Child, curItem.NeedsMap, curItem.IdenticalToChild, curItem.ParentNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ctn := range curItem.MergedChildren {
|
||||||
|
m := 0 // position in merged needs map
|
||||||
|
p := 0 // position in parent needs map
|
||||||
|
for p < len(ctn.NeedsMap) {
|
||||||
|
if ctn.NeedsMap[p].Cur == curItem.NeedsMap[m].Cur {
|
||||||
|
ctn.NeedsMap[p].Commit = curItem.NeedsMap[m].Commit
|
||||||
|
m++
|
||||||
|
p++
|
||||||
|
} else if ctn.NeedsMap[p].Cur < curItem.NeedsMap[m].Cur {
|
||||||
|
p++
|
||||||
|
} else {
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finished, err := applyNeeds(ctn.Child, ctn.NeedsMap, ctn.IdenticalToChild, ctn.ParentNo)
|
||||||
|
if finished || err != nil {
|
||||||
|
return finished, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyNeeds(child *queueItem, needsMap []lineMap, identicalToChild bool, parentNo int) (bool, error) {
|
||||||
|
if identicalToChild {
|
||||||
|
for i := range child.NeedsMap {
|
||||||
|
l := &child.NeedsMap[i]
|
||||||
|
if l.Cur != needsMap[i].Cur || l.Orig != needsMap[i].Orig {
|
||||||
|
return false, errors.New("needsMap isn't the same? Why not??")
|
||||||
|
}
|
||||||
|
if l.Commit == nil || parentNo < l.FromParentNo {
|
||||||
|
l.Commit = needsMap[i].Commit
|
||||||
|
l.FromParentNo = parentNo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i := 0
|
||||||
|
out:
|
||||||
|
for j := range child.NeedsMap {
|
||||||
|
l := &child.NeedsMap[j]
|
||||||
|
for needsMap[i].Orig < l.Cur {
|
||||||
|
i++
|
||||||
|
if i == len(needsMap) {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.Cur == needsMap[i].Orig {
|
||||||
|
if l.Commit == nil || parentNo < l.FromParentNo {
|
||||||
|
l.Commit = needsMap[i].Commit
|
||||||
|
l.FromParentNo = parentNo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child.numParentsNeedResolving--
|
||||||
|
if child.numParentsNeedResolving == 0 {
|
||||||
|
finished, err := finishNeeds(child)
|
||||||
|
if finished || err != nil {
|
||||||
|
return finished, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String prints the results of a Blame using git-blame's style.
|
||||||
|
func (b BlameResult) String() string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
file, err := b.fRev.File(b.path)
|
|
||||||
if err != nil {
|
|
||||||
panic("PrettyPrint: internal error in repo.Data")
|
|
||||||
}
|
|
||||||
contents, err := file.Contents()
|
|
||||||
if err != nil {
|
|
||||||
panic("PrettyPrint: internal error in repo.Data")
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(contents, "\n")
|
|
||||||
// max line number length
|
// max line number length
|
||||||
mlnl := len(strconv.Itoa(len(lines)))
|
mlnl := len(strconv.Itoa(len(b.Lines)))
|
||||||
// max author length
|
// max author length
|
||||||
mal := b.maxAuthorLength()
|
mal := b.maxAuthorLength()
|
||||||
format := fmt.Sprintf("%%s (%%-%ds %%%dd) %%s\n",
|
format := fmt.Sprintf("%%s (%%-%ds %%s %%%dd) %%s\n", mal, mlnl)
|
||||||
mal, mlnl)
|
|
||||||
|
|
||||||
fVs := b.graph[len(b.graph)-1]
|
for ln := range b.Lines {
|
||||||
for ln, v := range fVs {
|
_, _ = fmt.Fprintf(&buf, format, b.Lines[ln].Hash.String()[:8],
|
||||||
fmt.Fprintf(&buf, format, v.Hash.String()[:8],
|
b.Lines[ln].AuthorName, b.Lines[ln].Date.Format("2006-01-02 15:04:05 -0700"), ln+1, b.Lines[ln].Text)
|
||||||
prettyPrintAuthor(fVs[ln]), ln+1, lines[ln])
|
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility function to pretty print the author.
|
|
||||||
func prettyPrintAuthor(c *object.Commit) string {
|
|
||||||
return fmt.Sprintf("%s %s", c.Author.Name, c.Author.When.Format("2006-01-02"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// utility function to calculate the number of runes needed
|
// utility function to calculate the number of runes needed
|
||||||
// to print the longest author name in the blame of a file.
|
// to print the longest author name in the blame of a file.
|
||||||
func (b *blame) maxAuthorLength() int {
|
func (b BlameResult) maxAuthorLength() int {
|
||||||
memo := make(map[plumbing.Hash]struct{}, len(b.graph)-1)
|
|
||||||
fVs := b.graph[len(b.graph)-1]
|
|
||||||
m := 0
|
m := 0
|
||||||
for ln := range fVs {
|
for ln := range b.Lines {
|
||||||
if _, ok := memo[fVs[ln].Hash]; ok {
|
m = max(m, utf8.RuneCountInString(b.Lines[ln].AuthorName))
|
||||||
continue
|
|
||||||
}
|
|
||||||
memo[fVs[ln].Hash] = struct{}{}
|
|
||||||
m = max(m, utf8.RuneCountInString(prettyPrintAuthor(fVs[ln])))
|
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func max(a, b int) int {
|
func max(a, b int) int {
|
||||||
if a > b {
|
if a > b {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type childToNeedsMap struct {
|
||||||
|
Child *queueItem
|
||||||
|
NeedsMap []lineMap
|
||||||
|
IdenticalToChild bool
|
||||||
|
ParentNo int
|
||||||
|
}
|
||||||
|
|
||||||
|
type queueItem struct {
|
||||||
|
Child *queueItem
|
||||||
|
MergedChildren []childToNeedsMap
|
||||||
|
Commit *object.Commit
|
||||||
|
path string
|
||||||
|
Contents string
|
||||||
|
NeedsMap []lineMap
|
||||||
|
numParentsNeedResolving int
|
||||||
|
IdenticalToChild bool
|
||||||
|
ParentNo int
|
||||||
|
}
|
||||||
|
|
||||||
|
type priorityQueueImp []*queueItem
|
||||||
|
|
||||||
|
func (pq *priorityQueueImp) Len() int { return len(*pq) }
|
||||||
|
func (pq *priorityQueueImp) Less(i, j int) bool {
|
||||||
|
return !(*pq)[i].Commit.Less((*pq)[j].Commit)
|
||||||
|
}
|
||||||
|
func (pq *priorityQueueImp) Swap(i, j int) { (*pq)[i], (*pq)[j] = (*pq)[j], (*pq)[i] }
|
||||||
|
func (pq *priorityQueueImp) Push(x any) { *pq = append(*pq, x.(*queueItem)) }
|
||||||
|
func (pq *priorityQueueImp) Pop() any {
|
||||||
|
n := len(*pq)
|
||||||
|
ret := (*pq)[n-1]
|
||||||
|
(*pq)[n-1] = nil // ovoid memory leak
|
||||||
|
*pq = (*pq)[0 : n-1]
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
func (pq *priorityQueueImp) Peek() *object.Commit {
|
||||||
|
if len(*pq) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (*pq)[0].Commit
|
||||||
|
}
|
||||||
|
|
||||||
|
type priorityQueue priorityQueueImp
|
||||||
|
|
||||||
|
func (pq *priorityQueue) Init() { heap.Init((*priorityQueueImp)(pq)) }
|
||||||
|
func (pq *priorityQueue) Len() int { return (*priorityQueueImp)(pq).Len() }
|
||||||
|
func (pq *priorityQueue) Push(c *queueItem) {
|
||||||
|
heap.Push((*priorityQueueImp)(pq), c)
|
||||||
|
}
|
||||||
|
func (pq *priorityQueue) Pop() *queueItem {
|
||||||
|
return heap.Pop((*priorityQueueImp)(pq)).(*queueItem)
|
||||||
|
}
|
||||||
|
func (pq *priorityQueue) Peek() *object.Commit { return (*priorityQueueImp)(pq).Peek() }
|
||||||
|
|
||||||
|
type parentCommit struct {
|
||||||
|
Commit *object.Commit
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parentsContainingPath(path string, c *object.Commit) ([]parentCommit, error) {
|
||||||
|
// TODO: benchmark this method making git.object.Commit.parent public instead of using
|
||||||
|
// an iterator
|
||||||
|
var result []parentCommit
|
||||||
|
iter := c.Parents()
|
||||||
|
for {
|
||||||
|
parent, err := iter.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := parent.File(path); err == nil {
|
||||||
|
result = append(result, parentCommit{parent, path})
|
||||||
|
} else {
|
||||||
|
// look for renames
|
||||||
|
patch, err := parent.Patch(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if patch != nil {
|
||||||
|
for _, fp := range patch.FilePatches() {
|
||||||
|
from, to := fp.Files()
|
||||||
|
if from != nil && to != nil && to.Path() == path {
|
||||||
|
result = append(result, parentCommit{parent, from.Path()})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func blobHash(path string, commit *object.Commit) (plumbing.Hash, error) {
|
||||||
|
file, err := commit.File(path)
|
||||||
|
if err != nil {
|
||||||
|
return plumbing.ZeroHash, err
|
||||||
|
}
|
||||||
|
return file.Hash, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
29
vendor/github.com/go-git/go-git/v5/internal/path_util/path_util.go
generated
vendored
Normal file
29
vendor/github.com/go-git/go-git/v5/internal/path_util/path_util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package path_util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReplaceTildeWithHome(path string) (string, error) {
|
||||||
|
if strings.HasPrefix(path, "~") {
|
||||||
|
firstSlash := strings.Index(path, "/")
|
||||||
|
if firstSlash == 1 {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return path, err
|
||||||
|
}
|
||||||
|
return strings.Replace(path, "~", home, 1), nil
|
||||||
|
} else if firstSlash > 1 {
|
||||||
|
username := path[1:firstSlash]
|
||||||
|
userAccount, err := user.Lookup(username)
|
||||||
|
if err != nil {
|
||||||
|
return path, err
|
||||||
|
}
|
||||||
|
return strings.Replace(path, path[:firstSlash], userAccount.HomeDir, 1), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
@ -62,6 +62,9 @@ type CloneOptions struct {
|
||||||
// within, using their default settings. This option is ignored if the
|
// within, using their default settings. This option is ignored if the
|
||||||
// cloned repository does not have a worktree.
|
// cloned repository does not have a worktree.
|
||||||
RecurseSubmodules SubmoduleRescursivity
|
RecurseSubmodules SubmoduleRescursivity
|
||||||
|
// ShallowSubmodules limit cloning submodules to the 1 level of depth.
|
||||||
|
// It matches the git command --shallow-submodules.
|
||||||
|
ShallowSubmodules bool
|
||||||
// Progress is where the human readable information sent by the server is
|
// Progress is where the human readable information sent by the server is
|
||||||
// stored, if nil nothing is stored and the capability (if supported)
|
// stored, if nil nothing is stored and the capability (if supported)
|
||||||
// no-progress, is sent to the server to avoid send this information.
|
// no-progress, is sent to the server to avoid send this information.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
|
"github.com/go-git/go-git/v5/internal/path_util"
|
||||||
"github.com/go-git/go-git/v5/plumbing/format/config"
|
"github.com/go-git/go-git/v5/plumbing/format/config"
|
||||||
gioutil "github.com/go-git/go-git/v5/utils/ioutil"
|
gioutil "github.com/go-git/go-git/v5/utils/ioutil"
|
||||||
)
|
)
|
||||||
|
|
@ -25,6 +26,9 @@ const (
|
||||||
|
|
||||||
// readIgnoreFile reads a specific git ignore file.
|
// readIgnoreFile reads a specific git ignore file.
|
||||||
func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) {
|
func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) {
|
||||||
|
|
||||||
|
ignoreFile, _ = path_util.ReplaceTildeWithHome(ignoreFile)
|
||||||
|
|
||||||
f, err := fs.Open(fs.Join(append(path, ignoreFile)...))
|
f, err := fs.Open(fs.Join(append(path, ignoreFile)...))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ type pattern struct {
|
||||||
|
|
||||||
// ParsePattern parses a gitignore pattern string into the Pattern structure.
|
// ParsePattern parses a gitignore pattern string into the Pattern structure.
|
||||||
func ParsePattern(p string, domain []string) Pattern {
|
func ParsePattern(p string, domain []string) Pattern {
|
||||||
|
// storing domain, copy it to ensure it isn't changed externally
|
||||||
|
domain = append([]string(nil), domain...)
|
||||||
res := pattern{domain: domain}
|
res := pattern{domain: domain}
|
||||||
|
|
||||||
if strings.HasPrefix(p, inclusionPrefix) {
|
if strings.HasPrefix(p, inclusionPrefix) {
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,17 @@ func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
|
||||||
return openpgp.CheckArmoredDetachedSignature(keyring, er, signature, nil)
|
return openpgp.CheckArmoredDetachedSignature(keyring, er, signature, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Less defines a compare function to determine which commit is 'earlier' by:
|
||||||
|
// - First use Committer.When
|
||||||
|
// - If Committer.When are equal then use Author.When
|
||||||
|
// - If Author.When also equal then compare the string value of the hash
|
||||||
|
func (c *Commit) Less(rhs *Commit) bool {
|
||||||
|
return c.Committer.When.Before(rhs.Committer.When) ||
|
||||||
|
(c.Committer.When.Equal(rhs.Committer.When) &&
|
||||||
|
(c.Author.When.Before(rhs.Author.When) ||
|
||||||
|
(c.Author.When.Equal(rhs.Author.When) && bytes.Compare(c.Hash[:], rhs.Hash[:]) < 0)))
|
||||||
|
}
|
||||||
|
|
||||||
func indent(t string) string {
|
func indent(t string) string {
|
||||||
var output []string
|
var output []string
|
||||||
for _, line := range strings.Split(t, "\n") {
|
for _, line := range strings.Split(t, "\n") {
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@ func decodeFirstHash(p *advRefsDecoder) decoderStateFn {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Use object-format (when available) for hash size. Git 2.41+
|
||||||
if len(p.line) < hashSize {
|
if len(p.line) < hashSize {
|
||||||
p.error("cannot read hash, pkt-line too short")
|
p.error("cannot read hash, pkt-line too short")
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ func NewUploadPackRequestFromCapabilities(adv *capability.List) *UploadPackReque
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty a request if empty if Haves are contained in the Wants, or if Wants
|
// IsEmpty returns whether a request is empty - it is empty if Haves are contained
|
||||||
// length is zero
|
// in the Wants, or if Wants length is zero, and we don't have any shallows
|
||||||
func (r *UploadPackRequest) IsEmpty() bool {
|
func (r *UploadPackRequest) IsEmpty() bool {
|
||||||
return isSubset(r.Wants, r.Haves)
|
return isSubset(r.Wants, r.Haves) && len(r.Shallows) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSubset(needle []plumbing.Hash, haystack []plumbing.Hash) bool {
|
func isSubset(needle []plumbing.Hash, haystack []plumbing.Hash) bool {
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,11 @@ const (
|
||||||
symrefPrefix = "ref: "
|
symrefPrefix = "ref: "
|
||||||
)
|
)
|
||||||
|
|
||||||
// RefRevParseRules are a set of rules to parse references into short names.
|
// RefRevParseRules are a set of rules to parse references into short names, or expand into a full reference.
|
||||||
// These are the same rules as used by git in shorten_unambiguous_ref.
|
// These are the same rules as used by git in shorten_unambiguous_ref and expand_ref.
|
||||||
// See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417
|
// See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417
|
||||||
var RefRevParseRules = []string{
|
var RefRevParseRules = []string{
|
||||||
|
"%s",
|
||||||
"refs/%s",
|
"refs/%s",
|
||||||
"refs/tags/%s",
|
"refs/tags/%s",
|
||||||
"refs/heads/%s",
|
"refs/heads/%s",
|
||||||
|
|
@ -113,7 +114,7 @@ func (r ReferenceName) String() string {
|
||||||
func (r ReferenceName) Short() string {
|
func (r ReferenceName) Short() string {
|
||||||
s := string(r)
|
s := string(r)
|
||||||
res := s
|
res := s
|
||||||
for _, format := range RefRevParseRules {
|
for _, format := range RefRevParseRules[1:] {
|
||||||
_, err := fmt.Sscanf(s, format, &res)
|
_, err := fmt.Sscanf(s, format, &res)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,17 @@ func advertisedReferences(ctx context.Context, s *session, serviceName string) (
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Git 2.41+ returns a zero-id plus capabilities when an empty
|
||||||
|
// repository is being cloned. This skips the existing logic within
|
||||||
|
// advrefs_decode.decodeFirstHash, which expects a flush-pkt instead.
|
||||||
|
//
|
||||||
|
// This logic aligns with plumbing/transport/internal/common/common.go.
|
||||||
|
if ar.IsEmpty() &&
|
||||||
|
// Empty repositories are valid for git-receive-pack.
|
||||||
|
transport.ReceivePackServiceName != serviceName {
|
||||||
|
return nil, transport.ErrEmptyRemoteRepository
|
||||||
|
}
|
||||||
|
|
||||||
transport.FilterUnsupportedCapabilities(ar.Capabilities)
|
transport.FilterUnsupportedCapabilities(ar.Capabilities)
|
||||||
s.advRefs = ar
|
s.advRefs = ar
|
||||||
|
|
||||||
|
|
|
||||||
2
vendor/github.com/go-git/go-git/v5/plumbing/transport/internal/common/common.go
generated
vendored
2
vendor/github.com/go-git/go-git/v5/plumbing/transport/internal/common/common.go
generated
vendored
|
|
@ -232,7 +232,7 @@ func (s *session) handleAdvRefDecodeError(err error) error {
|
||||||
// UploadPack performs a request to the server to fetch a packfile. A reader is
|
// UploadPack performs a request to the server to fetch a packfile. A reader is
|
||||||
// returned with the packfile content. The reader must be closed after reading.
|
// returned with the packfile content. The reader must be closed after reading.
|
||||||
func (s *session) UploadPack(ctx context.Context, req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) {
|
func (s *session) UploadPack(ctx context.Context, req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) {
|
||||||
if req.IsEmpty() && len(req.Shallows) == 0 {
|
if req.IsEmpty() {
|
||||||
return nil, transport.ErrEmptyUploadPackRequest
|
return nil, transport.ErrEmptyUploadPackRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,264 +0,0 @@
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
|
||||||
"github.com/go-git/go-git/v5/utils/diff"
|
|
||||||
|
|
||||||
"github.com/sergi/go-diff/diffmatchpatch"
|
|
||||||
)
|
|
||||||
|
|
||||||
// References returns a slice of Commits for the file at "path", starting from
|
|
||||||
// the commit provided that contains the file from the provided path. The last
|
|
||||||
// commit into the returned slice is the commit where the file was created.
|
|
||||||
// If the provided commit does not contains the specified path, a nil slice is
|
|
||||||
// returned. The commits are sorted in commit order, newer to older.
|
|
||||||
//
|
|
||||||
// Caveats:
|
|
||||||
//
|
|
||||||
// - Moves and copies are not currently supported.
|
|
||||||
//
|
|
||||||
// - Cherry-picks are not detected unless there are no commits between them and
|
|
||||||
// therefore can appear repeated in the list. (see git path-id for hints on how
|
|
||||||
// to fix this).
|
|
||||||
func references(c *object.Commit, path string) ([]*object.Commit, error) {
|
|
||||||
var result []*object.Commit
|
|
||||||
seen := make(map[plumbing.Hash]struct{})
|
|
||||||
if err := walkGraph(&result, &seen, c, path); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO result should be returned without ordering
|
|
||||||
sortCommits(result)
|
|
||||||
|
|
||||||
// for merges of identical cherry-picks
|
|
||||||
return removeComp(path, result, equivalent)
|
|
||||||
}
|
|
||||||
|
|
||||||
type commitSorterer struct {
|
|
||||||
l []*object.Commit
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s commitSorterer) Len() int {
|
|
||||||
return len(s.l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s commitSorterer) Less(i, j int) bool {
|
|
||||||
return s.l[i].Committer.When.Before(s.l[j].Committer.When) ||
|
|
||||||
s.l[i].Committer.When.Equal(s.l[j].Committer.When) &&
|
|
||||||
s.l[i].Author.When.Before(s.l[j].Author.When)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s commitSorterer) Swap(i, j int) {
|
|
||||||
s.l[i], s.l[j] = s.l[j], s.l[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortCommits sorts a commit list by commit date, from older to newer.
|
|
||||||
func sortCommits(l []*object.Commit) {
|
|
||||||
s := &commitSorterer{l}
|
|
||||||
sort.Sort(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursive traversal of the commit graph, generating a linear history of the
|
|
||||||
// path.
|
|
||||||
func walkGraph(result *[]*object.Commit, seen *map[plumbing.Hash]struct{}, current *object.Commit, path string) error {
|
|
||||||
// check and update seen
|
|
||||||
if _, ok := (*seen)[current.Hash]; ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
(*seen)[current.Hash] = struct{}{}
|
|
||||||
|
|
||||||
// if the path is not in the current commit, stop searching.
|
|
||||||
if _, err := current.File(path); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// optimization: don't traverse branches that does not
|
|
||||||
// contain the path.
|
|
||||||
parents, err := parentsContainingPath(path, current)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch len(parents) {
|
|
||||||
// if the path is not found in any of its parents, the path was
|
|
||||||
// created by this commit; we must add it to the revisions list and
|
|
||||||
// stop searching. This includes the case when current is the
|
|
||||||
// initial commit.
|
|
||||||
case 0:
|
|
||||||
*result = append(*result, current)
|
|
||||||
return nil
|
|
||||||
case 1: // only one parent contains the path
|
|
||||||
// if the file contents has change, add the current commit
|
|
||||||
different, err := differentContents(path, current, parents)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(different) == 1 {
|
|
||||||
*result = append(*result, current)
|
|
||||||
}
|
|
||||||
// in any case, walk the parent
|
|
||||||
return walkGraph(result, seen, parents[0], path)
|
|
||||||
default: // more than one parent contains the path
|
|
||||||
// TODO: detect merges that had a conflict, because they must be
|
|
||||||
// included in the result here.
|
|
||||||
for _, p := range parents {
|
|
||||||
err := walkGraph(result, seen, p, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parentsContainingPath(path string, c *object.Commit) ([]*object.Commit, error) {
|
|
||||||
// TODO: benchmark this method making git.object.Commit.parent public instead of using
|
|
||||||
// an iterator
|
|
||||||
var result []*object.Commit
|
|
||||||
iter := c.Parents()
|
|
||||||
for {
|
|
||||||
parent, err := iter.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := parent.File(path); err == nil {
|
|
||||||
result = append(result, parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns an slice of the commits in "cs" that has the file "path", but with different
|
|
||||||
// contents than what can be found in "c".
|
|
||||||
func differentContents(path string, c *object.Commit, cs []*object.Commit) ([]*object.Commit, error) {
|
|
||||||
result := make([]*object.Commit, 0, len(cs))
|
|
||||||
h, found := blobHash(path, c)
|
|
||||||
if !found {
|
|
||||||
return nil, object.ErrFileNotFound
|
|
||||||
}
|
|
||||||
for _, cx := range cs {
|
|
||||||
if hx, found := blobHash(path, cx); found && h != hx {
|
|
||||||
result = append(result, cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// blobHash returns the hash of a path in a commit
|
|
||||||
func blobHash(path string, commit *object.Commit) (hash plumbing.Hash, found bool) {
|
|
||||||
file, err := commit.File(path)
|
|
||||||
if err != nil {
|
|
||||||
var empty plumbing.Hash
|
|
||||||
return empty, found
|
|
||||||
}
|
|
||||||
return file.Hash, true
|
|
||||||
}
|
|
||||||
|
|
||||||
type contentsComparatorFn func(path string, a, b *object.Commit) (bool, error)
|
|
||||||
|
|
||||||
// Returns a new slice of commits, with duplicates removed. Expects a
|
|
||||||
// sorted commit list. Duplication is defined according to "comp". It
|
|
||||||
// will always keep the first commit of a series of duplicated commits.
|
|
||||||
func removeComp(path string, cs []*object.Commit, comp contentsComparatorFn) ([]*object.Commit, error) {
|
|
||||||
result := make([]*object.Commit, 0, len(cs))
|
|
||||||
if len(cs) == 0 {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
result = append(result, cs[0])
|
|
||||||
for i := 1; i < len(cs); i++ {
|
|
||||||
equals, err := comp(path, cs[i], cs[i-1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !equals {
|
|
||||||
result = append(result, cs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equivalent commits are commits whose patch is the same.
|
|
||||||
func equivalent(path string, a, b *object.Commit) (bool, error) {
|
|
||||||
numParentsA := a.NumParents()
|
|
||||||
numParentsB := b.NumParents()
|
|
||||||
|
|
||||||
// the first commit is not equivalent to anyone
|
|
||||||
// and "I think" merges can not be equivalent to anything
|
|
||||||
if numParentsA != 1 || numParentsB != 1 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
diffsA, err := patch(a, path)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
diffsB, err := patch(b, path)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return sameDiffs(diffsA, diffsB), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func patch(c *object.Commit, path string) ([]diffmatchpatch.Diff, error) {
|
|
||||||
// get contents of the file in the commit
|
|
||||||
file, err := c.File(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
content, err := file.Contents()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get contents of the file in the first parent of the commit
|
|
||||||
var contentParent string
|
|
||||||
iter := c.Parents()
|
|
||||||
parent, err := iter.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
file, err = parent.File(path)
|
|
||||||
if err != nil {
|
|
||||||
contentParent = ""
|
|
||||||
} else {
|
|
||||||
contentParent, err = file.Contents()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare the contents of parent and child
|
|
||||||
return diff.Do(content, contentParent), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameDiffs(a, b []diffmatchpatch.Diff) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range a {
|
|
||||||
if !sameDiff(a[i], b[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameDiff(a, b diffmatchpatch.Diff) bool {
|
|
||||||
if a.Type != b.Type {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch a.Type {
|
|
||||||
case 0:
|
|
||||||
return countLines(a.Text) == countLines(b.Text)
|
|
||||||
case 1, -1:
|
|
||||||
return a.Text == b.Text
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -224,11 +224,13 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rs != nil {
|
||||||
if err = rs.Error(); err != nil {
|
if err = rs.Error(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return r.updateRemoteReferenceStorage(req, rs)
|
return r.updateRemoteReferenceStorage(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool {
|
func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool {
|
||||||
|
|
@ -347,7 +349,6 @@ func (r *Remote) newReferenceUpdateRequest(
|
||||||
|
|
||||||
func (r *Remote) updateRemoteReferenceStorage(
|
func (r *Remote) updateRemoteReferenceStorage(
|
||||||
req *packp.ReferenceUpdateRequest,
|
req *packp.ReferenceUpdateRequest,
|
||||||
result *packp.ReportStatus,
|
|
||||||
) error {
|
) error {
|
||||||
|
|
||||||
for _, spec := range r.c.Fetch {
|
for _, spec := range r.c.Fetch {
|
||||||
|
|
@ -445,7 +446,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
refs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
|
refs, specToRefs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -457,9 +458,9 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Wants, err = getWants(r.s, refs)
|
req.Wants, err = getWants(r.s, refs, o.Depth)
|
||||||
if len(req.Wants) > 0 {
|
if len(req.Wants) > 0 {
|
||||||
req.Haves, err = getHaves(localRefs, remoteRefs, r.s)
|
req.Haves, err = getHaves(localRefs, remoteRefs, r.s, o.Depth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -469,7 +470,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, o.Tags, o.Force)
|
updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, specToRefs, o.Tags, o.Force)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -837,6 +838,7 @@ func getHavesFromRef(
|
||||||
remoteRefs map[plumbing.Hash]bool,
|
remoteRefs map[plumbing.Hash]bool,
|
||||||
s storage.Storer,
|
s storage.Storer,
|
||||||
haves map[plumbing.Hash]bool,
|
haves map[plumbing.Hash]bool,
|
||||||
|
depth int,
|
||||||
) error {
|
) error {
|
||||||
h := ref.Hash()
|
h := ref.Hash()
|
||||||
if haves[h] {
|
if haves[h] {
|
||||||
|
|
@ -862,7 +864,13 @@ func getHavesFromRef(
|
||||||
// commits from the history of each ref.
|
// commits from the history of each ref.
|
||||||
walker := object.NewCommitPreorderIter(commit, haves, nil)
|
walker := object.NewCommitPreorderIter(commit, haves, nil)
|
||||||
toVisit := maxHavesToVisitPerRef
|
toVisit := maxHavesToVisitPerRef
|
||||||
return walker.ForEach(func(c *object.Commit) error {
|
// But only need up to the requested depth
|
||||||
|
if depth > 0 && depth < maxHavesToVisitPerRef {
|
||||||
|
toVisit = depth
|
||||||
|
}
|
||||||
|
// It is safe to ignore any error here as we are just trying to find the references that we already have
|
||||||
|
// An example of a legitimate failure is we have a shallow clone and don't have the previous commit(s)
|
||||||
|
_ = walker.ForEach(func(c *object.Commit) error {
|
||||||
haves[c.Hash] = true
|
haves[c.Hash] = true
|
||||||
toVisit--
|
toVisit--
|
||||||
// If toVisit starts out at 0 (indicating there is no
|
// If toVisit starts out at 0 (indicating there is no
|
||||||
|
|
@ -873,12 +881,15 @@ func getHavesFromRef(
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHaves(
|
func getHaves(
|
||||||
localRefs []*plumbing.Reference,
|
localRefs []*plumbing.Reference,
|
||||||
remoteRefStorer storer.ReferenceStorer,
|
remoteRefStorer storer.ReferenceStorer,
|
||||||
s storage.Storer,
|
s storage.Storer,
|
||||||
|
depth int,
|
||||||
) ([]plumbing.Hash, error) {
|
) ([]plumbing.Hash, error) {
|
||||||
haves := map[plumbing.Hash]bool{}
|
haves := map[plumbing.Hash]bool{}
|
||||||
|
|
||||||
|
|
@ -899,7 +910,7 @@ func getHaves(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = getHavesFromRef(ref, remoteRefs, s, haves)
|
err = getHavesFromRef(ref, remoteRefs, s, haves, depth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -919,42 +930,41 @@ func calculateRefs(
|
||||||
spec []config.RefSpec,
|
spec []config.RefSpec,
|
||||||
remoteRefs storer.ReferenceStorer,
|
remoteRefs storer.ReferenceStorer,
|
||||||
tagMode TagMode,
|
tagMode TagMode,
|
||||||
) (memory.ReferenceStorage, error) {
|
) (memory.ReferenceStorage, [][]*plumbing.Reference, error) {
|
||||||
if tagMode == AllTags {
|
if tagMode == AllTags {
|
||||||
spec = append(spec, refspecAllTags)
|
spec = append(spec, refspecAllTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
refs := make(memory.ReferenceStorage)
|
refs := make(memory.ReferenceStorage)
|
||||||
for _, s := range spec {
|
// list of references matched for each spec
|
||||||
if err := doCalculateRefs(s, remoteRefs, refs); err != nil {
|
specToRefs := make([][]*plumbing.Reference, len(spec))
|
||||||
return nil, err
|
for i := range spec {
|
||||||
|
var err error
|
||||||
|
specToRefs[i], err = doCalculateRefs(spec[i], remoteRefs, refs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return refs, nil
|
return refs, specToRefs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func doCalculateRefs(
|
func doCalculateRefs(
|
||||||
s config.RefSpec,
|
s config.RefSpec,
|
||||||
remoteRefs storer.ReferenceStorer,
|
remoteRefs storer.ReferenceStorer,
|
||||||
refs memory.ReferenceStorage,
|
refs memory.ReferenceStorage,
|
||||||
) error {
|
) ([]*plumbing.Reference, error) {
|
||||||
iter, err := remoteRefs.IterReferences()
|
var refList []*plumbing.Reference
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.IsExactSHA1() {
|
if s.IsExactSHA1() {
|
||||||
ref := plumbing.NewHashReference(s.Dst(""), plumbing.NewHash(s.Src()))
|
ref := plumbing.NewHashReference(s.Dst(""), plumbing.NewHash(s.Src()))
|
||||||
return refs.SetReference(ref)
|
|
||||||
|
refList = append(refList, ref)
|
||||||
|
return refList, refs.SetReference(ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
var matched bool
|
var matched bool
|
||||||
err = iter.ForEach(func(ref *plumbing.Reference) error {
|
onMatched := func(ref *plumbing.Reference) error {
|
||||||
if !s.Match(ref.Name()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref.Type() == plumbing.SymbolicReference {
|
if ref.Type() == plumbing.SymbolicReference {
|
||||||
target, err := storer.ResolveReference(remoteRefs, ref.Name())
|
target, err := storer.ResolveReference(remoteRefs, ref.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -969,29 +979,48 @@ func doCalculateRefs(
|
||||||
}
|
}
|
||||||
|
|
||||||
matched = true
|
matched = true
|
||||||
if err := refs.SetReference(ref); err != nil {
|
refList = append(refList, ref)
|
||||||
return err
|
return refs.SetReference(ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.IsWildcard() {
|
var ret error
|
||||||
return storer.ErrStop
|
if s.IsWildcard() {
|
||||||
|
iter, err := remoteRefs.IterReferences()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ret = iter.ForEach(func(ref *plumbing.Reference) error {
|
||||||
|
if !s.Match(ref.Name()) {
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return onMatched(ref)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
var resolvedRef *plumbing.Reference
|
||||||
|
src := s.Src()
|
||||||
|
resolvedRef, ret = expand_ref(remoteRefs, plumbing.ReferenceName(src))
|
||||||
|
if ret == nil {
|
||||||
|
ret = onMatched(resolvedRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !matched && !s.IsWildcard() {
|
if !matched && !s.IsWildcard() {
|
||||||
return NoMatchingRefSpecError{refSpec: s}
|
return nil, NoMatchingRefSpecError{refSpec: s}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return refList, ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWants(localStorer storage.Storer, refs memory.ReferenceStorage) ([]plumbing.Hash, error) {
|
func getWants(localStorer storage.Storer, refs memory.ReferenceStorage, depth int) ([]plumbing.Hash, error) {
|
||||||
|
// If depth is anything other than 1 and the repo has shallow commits then just because we have the commit
|
||||||
|
// at the reference doesn't mean that we don't still need to fetch the parents
|
||||||
shallow := false
|
shallow := false
|
||||||
|
if depth != 1 {
|
||||||
if s, _ := localStorer.Shallow(); len(s) > 0 {
|
if s, _ := localStorer.Shallow(); len(s) > 0 {
|
||||||
shallow = true
|
shallow = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
wants := map[plumbing.Hash]bool{}
|
wants := map[plumbing.Hash]bool{}
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
|
|
@ -1144,27 +1173,28 @@ func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.P
|
||||||
func (r *Remote) updateLocalReferenceStorage(
|
func (r *Remote) updateLocalReferenceStorage(
|
||||||
specs []config.RefSpec,
|
specs []config.RefSpec,
|
||||||
fetchedRefs, remoteRefs memory.ReferenceStorage,
|
fetchedRefs, remoteRefs memory.ReferenceStorage,
|
||||||
|
specToRefs [][]*plumbing.Reference,
|
||||||
tagMode TagMode,
|
tagMode TagMode,
|
||||||
force bool,
|
force bool,
|
||||||
) (updated bool, err error) {
|
) (updated bool, err error) {
|
||||||
isWildcard := true
|
isWildcard := true
|
||||||
forceNeeded := false
|
forceNeeded := false
|
||||||
|
|
||||||
for _, spec := range specs {
|
for i, spec := range specs {
|
||||||
if !spec.IsWildcard() {
|
if !spec.IsWildcard() {
|
||||||
isWildcard = false
|
isWildcard = false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ref := range fetchedRefs {
|
for _, ref := range specToRefs[i] {
|
||||||
if !spec.Match(ref.Name()) && !spec.IsExactSHA1() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref.Type() != plumbing.HashReference {
|
if ref.Type() != plumbing.HashReference {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
localName := spec.Dst(ref.Name())
|
localName := spec.Dst(ref.Name())
|
||||||
|
// If localName doesn't start with "refs/" then treat as a branch.
|
||||||
|
if !strings.HasPrefix(localName.String(), "refs/") {
|
||||||
|
localName = plumbing.NewBranchReferenceName(localName.String())
|
||||||
|
}
|
||||||
old, _ := storer.ResolveReference(r.s, localName)
|
old, _ := storer.ResolveReference(r.s, localName)
|
||||||
new := plumbing.NewHashReference(localName, ref.Hash())
|
new := plumbing.NewHashReference(localName, ref.Hash())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/go-git/go-billy/v5/osfs"
|
"github.com/go-git/go-billy/v5/osfs"
|
||||||
"github.com/go-git/go-billy/v5/util"
|
"github.com/go-git/go-billy/v5/util"
|
||||||
"github.com/go-git/go-git/v5/config"
|
"github.com/go-git/go-git/v5/config"
|
||||||
|
"github.com/go-git/go-git/v5/internal/path_util"
|
||||||
"github.com/go-git/go-git/v5/internal/revision"
|
"github.com/go-git/go-git/v5/internal/revision"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/cache"
|
"github.com/go-git/go-git/v5/plumbing/cache"
|
||||||
|
|
@ -322,6 +323,11 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, err error) {
|
func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, err error) {
|
||||||
|
path, err = path_util.ReplaceTildeWithHome(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if path, err = filepath.Abs(path); err != nil {
|
if path, err = filepath.Abs(path); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -916,6 +922,12 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
|
||||||
if o.RecurseSubmodules != NoRecurseSubmodules {
|
if o.RecurseSubmodules != NoRecurseSubmodules {
|
||||||
if err := w.updateSubmodules(&SubmoduleUpdateOptions{
|
if err := w.updateSubmodules(&SubmoduleUpdateOptions{
|
||||||
RecurseSubmodules: o.RecurseSubmodules,
|
RecurseSubmodules: o.RecurseSubmodules,
|
||||||
|
Depth: func() int {
|
||||||
|
if o.ShallowSubmodules {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}(),
|
||||||
Auth: o.Auth,
|
Auth: o.Auth,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -967,7 +979,6 @@ func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec {
|
||||||
case o.SingleBranch && o.ReferenceName == plumbing.HEAD:
|
case o.SingleBranch && o.ReferenceName == plumbing.HEAD:
|
||||||
return []config.RefSpec{
|
return []config.RefSpec{
|
||||||
config.RefSpec(fmt.Sprintf(refspecSingleBranchHEAD, o.RemoteName)),
|
config.RefSpec(fmt.Sprintf(refspecSingleBranchHEAD, o.RemoteName)),
|
||||||
config.RefSpec(fmt.Sprintf(refspecSingleBranch, plumbing.Master.Short(), o.RemoteName)),
|
|
||||||
}
|
}
|
||||||
case o.SingleBranch:
|
case o.SingleBranch:
|
||||||
return []config.RefSpec{
|
return []config.RefSpec{
|
||||||
|
|
@ -1029,21 +1040,9 @@ func (r *Repository) fetchAndUpdateReferences(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolvedRef *plumbing.Reference
|
resolvedRef, err := expand_ref(remoteRefs, ref)
|
||||||
// return error from checking the raw ref passed in
|
|
||||||
var rawRefError error
|
|
||||||
for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) {
|
|
||||||
resolvedRef, err = storer.ResolveReference(remoteRefs, plumbing.ReferenceName(fmt.Sprintf(rule, ref)))
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
} else if rawRefError == nil {
|
|
||||||
rawRefError = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, rawRefError
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
refsUpdated, err := r.updateReferences(remote.c.Fetch, resolvedRef)
|
refsUpdated, err := r.updateReferences(remote.c.Fetch, resolvedRef)
|
||||||
|
|
@ -1489,6 +1488,23 @@ func (r *Repository) Worktree() (*Worktree, error) {
|
||||||
return &Worktree{r: r, Filesystem: r.wt}, nil
|
return &Worktree{r: r, Filesystem: r.wt}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) {
|
||||||
|
// For improving troubleshooting, this preserves the error for the provided `ref`,
|
||||||
|
// and returns the error for that specific ref in case all parse rules fails.
|
||||||
|
var ret error
|
||||||
|
for _, rule := range plumbing.RefRevParseRules {
|
||||||
|
resolvedRef, err := storer.ResolveReference(s, plumbing.ReferenceName(fmt.Sprintf(rule, ref)))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return resolvedRef, nil
|
||||||
|
} else if ret == nil {
|
||||||
|
ret = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ret
|
||||||
|
}
|
||||||
|
|
||||||
// ResolveRevision resolves revision to corresponding hash. It will always
|
// ResolveRevision resolves revision to corresponding hash. It will always
|
||||||
// resolve to a commit hash, not a tree or annotated tag.
|
// resolve to a commit hash, not a tree or annotated tag.
|
||||||
//
|
//
|
||||||
|
|
@ -1518,13 +1534,9 @@ func (r *Repository) ResolveRevision(in plumbing.Revision) (*plumbing.Hash, erro
|
||||||
|
|
||||||
tryHashes = append(tryHashes, r.resolveHashPrefix(string(revisionRef))...)
|
tryHashes = append(tryHashes, r.resolveHashPrefix(string(revisionRef))...)
|
||||||
|
|
||||||
for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) {
|
ref, err := expand_ref(r.Storer, plumbing.ReferenceName(revisionRef))
|
||||||
ref, err := storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef)))
|
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tryHashes = append(tryHashes, ref.Hash())
|
tryHashes = append(tryHashes, ref.Hash())
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// in ambiguous cases, `git rev-parse` will emit a warning, but
|
// in ambiguous cases, `git rev-parse` will emit a warning, but
|
||||||
|
|
|
||||||
|
|
@ -582,7 +582,9 @@ func (d *DotGit) hasIncomingObjects() bool {
|
||||||
directoryContents, err := d.fs.ReadDir(objectsPath)
|
directoryContents, err := d.fs.ReadDir(objectsPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, file := range directoryContents {
|
for _, file := range directoryContents {
|
||||||
if strings.HasPrefix(file.Name(), "incoming-") && file.IsDir() {
|
if file.IsDir() && (strings.HasPrefix(file.Name(), "tmp_objdir-incoming-") ||
|
||||||
|
// Before Git 2.35 incoming commits directory had another prefix
|
||||||
|
strings.HasPrefix(file.Name(), "incoming-")) {
|
||||||
d.incomingDirName = file.Name()
|
d.incomingDirName = file.Name()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
"github.com/go-git/go-git/v5/config"
|
"github.com/go-git/go-git/v5/config"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/format/index"
|
"github.com/go-git/go-git/v5/plumbing/format/index"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -133,29 +133,29 @@ func (s *Submodule) Repository() (*Repository, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleURL, err := url.Parse(s.c.URL)
|
moduleEndpoint, err := transport.NewEndpoint(s.c.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !path.IsAbs(moduleURL.Path) {
|
if !path.IsAbs(moduleEndpoint.Path) && moduleEndpoint.Protocol == "file" {
|
||||||
remotes, err := s.w.r.Remotes()
|
remotes, err := s.w.r.Remotes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rootURL, err := url.Parse(remotes[0].c.URLs[0])
|
rootEndpoint, err := transport.NewEndpoint(remotes[0].c.URLs[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rootURL.Path = path.Join(rootURL.Path, moduleURL.Path)
|
rootEndpoint.Path = path.Join(rootEndpoint.Path, moduleEndpoint.Path)
|
||||||
*moduleURL = *rootURL
|
*moduleEndpoint = *rootEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = r.CreateRemote(&config.RemoteConfig{
|
_, err = r.CreateRemote(&config.RemoteConfig{
|
||||||
Name: DefaultRemoteName,
|
Name: DefaultRemoteName,
|
||||||
URLs: []string{moduleURL.String()},
|
URLs: []string{moduleEndpoint.String()},
|
||||||
})
|
})
|
||||||
|
|
||||||
return r, err
|
return r, err
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,10 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
directory = filepath.ToSlash(filepath.Clean(directory))
|
||||||
|
|
||||||
for name := range s {
|
for name := range s {
|
||||||
if !isPathInDirectory(name, filepath.ToSlash(filepath.Clean(directory))) {
|
if !isPathInDirectory(name, directory) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,32 +294,14 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !added && a {
|
added = added || a
|
||||||
added = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPathInDirectory(path, directory string) bool {
|
func isPathInDirectory(path, directory string) bool {
|
||||||
ps := strings.Split(path, "/")
|
return directory == "." || strings.HasPrefix(path, directory+"/")
|
||||||
ds := strings.Split(directory, "/")
|
|
||||||
|
|
||||||
if len(ds) == 1 && ds[0] == "." {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ps) < len(ds) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(ds); i++ {
|
|
||||||
if ps[i] != ds[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddWithOptions file contents to the index, updates the index using the
|
// AddWithOptions file contents to the index, updates the index using the
|
||||||
|
|
|
||||||
|
|
@ -449,10 +449,11 @@ github.com/go-git/go-billy/v5/helper/polyfill
|
||||||
github.com/go-git/go-billy/v5/memfs
|
github.com/go-git/go-billy/v5/memfs
|
||||||
github.com/go-git/go-billy/v5/osfs
|
github.com/go-git/go-billy/v5/osfs
|
||||||
github.com/go-git/go-billy/v5/util
|
github.com/go-git/go-billy/v5/util
|
||||||
# github.com/go-git/go-git/v5 v5.7.0
|
# github.com/go-git/go-git/v5 v5.8.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/go-git/go-git/v5
|
github.com/go-git/go-git/v5
|
||||||
github.com/go-git/go-git/v5/config
|
github.com/go-git/go-git/v5/config
|
||||||
|
github.com/go-git/go-git/v5/internal/path_util
|
||||||
github.com/go-git/go-git/v5/internal/revision
|
github.com/go-git/go-git/v5/internal/revision
|
||||||
github.com/go-git/go-git/v5/internal/url
|
github.com/go-git/go-git/v5/internal/url
|
||||||
github.com/go-git/go-git/v5/plumbing
|
github.com/go-git/go-git/v5/plumbing
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue