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:
dependabot[bot] 2023-07-24 14:56:19 -07:00 committed by GitHub
parent b33b7c432c
commit b314518f0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 696 additions and 543 deletions

2
go.mod
View File

@ -19,7 +19,7 @@ require (
github.com/containerd/cgroups v1.1.0 // indirect
github.com/docker/docker v23.0.5+incompatible
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/google/go-cmp v0.5.9
github.com/google/go-containerregistry v0.15.2

4
go.sum
View File

@ -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/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/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE=
github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
github.com/go-git/go-git/v5 v5.8.0 h1:Rc543s6Tyq+YcyPwZRvU4jzZGM8rB/wWu94TnTIYALQ=
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/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=

38
vendor/github.com/go-git/go-git/v5/SECURITY.md generated vendored Normal file
View File

@ -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.

View File

@ -2,16 +2,18 @@ package git
import (
"bytes"
"container/heap"
"errors"
"fmt"
"io"
"strconv"
"strings"
"time"
"unicode/utf8"
"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"
)
// BlameResult represents the result of a Blame operation.
@ -29,53 +31,26 @@ type BlameResult struct {
func Blame(c *object.Commit, path string) (*BlameResult, error) {
// The file to blame is identified by the input arguments:
// 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
// revlist.New for that.
// When a diff is found it cannot immediately assume it came from that commit, as it may have come from 1 of its
// 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
// the history of the file.
// When encountering 2 parents that have made the same change to a file it will choose the parent that was merged
// 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
// commit. Assign that commit as the creator of all its lines.
//
// 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.
// This currently works on a line by line basis, if performance becomes an issue it could be changed to work with
// hunks rather than lines. Then when encountering diff hunks it would need to split them where necessary.
b := new(blame)
b.fRev = c
b.path = path
b.q = new(priorityQueue)
// get all the file revisions
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)
file, err := b.fRev.File(path)
if err != nil {
return nil, err
}
@ -83,13 +58,59 @@ func Blame(c *object.Commit, path string) (*BlameResult, error) {
if err != nil {
return nil, err
}
finalLength := len(finalLines)
// Each node (line) holds the commit where it was introduced or
// last modified. To achieve that we use the FORWARD algorithm
// described in Zimmermann, et al. "Mining Version Archives for
// Co-changed Lines", in proceedings of the Mining Software
// Repositories workshop, Shanghai, May 22-23, 2006.
lines, err := newLines(finalLines, b.sliceGraph(len(b.graph)-1))
needsMap := make([]lineMap, finalLength)
for i := range needsMap {
needsMap[i] = lineMap{i, i, nil, -1}
}
contents, err := file.Contents()
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 {
return nil, err
}
@ -105,6 +126,8 @@ func Blame(c *object.Commit, path string) (*BlameResult, error) {
type Line struct {
// Author is the email address of the last author that modified the line.
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 string
// Date is when the original text of the line was introduced
@ -113,31 +136,21 @@ type Line struct {
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{
Author: author,
Text: text,
Hash: hash,
Date: date,
Author: author,
AuthorName: authorName,
Text: text,
Hash: hash,
Date: date,
}
}
func newLines(contents []string, commits []*object.Commit) ([]*Line, error) {
lcontents := 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)
result := make([]*Line, 0, len(contents))
for i := range contents {
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,
))
}
@ -152,151 +165,426 @@ type blame struct {
path string
// the commit of the final revision of the file to blame
fRev *object.Commit
// the chain of revisions affecting the the file to blame
revs []*object.Commit
// the contents of the file across all its revisions
data []string
// the graph of the lines in the file across all the revisions
graph [][]*object.Commit
// resolved lines
lineToCommit []*object.Commit
// queue of commits that need resolving
q *priorityQueue
}
// calculate the history of a file "path", starting from commit "from", sorted by commit date.
func (b *blame) fillRevs() error {
var err error
b.revs, err = references(b.fRev, b.path)
return err
type lineMap struct {
Orig, Cur int
Commit *object.Commit
FromParentNo int
}
// build graph of a file from its revision history
func (b *blame) fillGraphAndData() error {
//TODO: not all commits are needed, only the current rev and the prev
b.graph = make([][]*object.Commit, len(b.revs))
b.data = make([]string, len(b.revs)) // file contents in all the revisions
// for every revision of the file, starting with the first
// one...
for i, rev := range b.revs {
func (b *blame) addBlames(curItems []*queueItem) (bool, error) {
curItem := curItems[0]
// Simple optimisation to merge paths, there is potential to go a bit further here and check for any duplicates
// not only if they are all the same.
if len(curItems) == 1 {
curItems = nil
} else if curItem.IdenticalToChild {
allSame := true
lenCurItems := len(curItems)
lowestParentNo := curItem.ParentNo
for i := 1; i < lenCurItems; i++ {
if !curItems[i].IdenticalToChild || curItem.Child != curItems[i].Child {
allSame = false
break
}
lowestParentNo = min(lowestParentNo, curItems[i].ParentNo)
}
if allSame {
curItem.Child.numParentsNeedResolving = curItem.Child.numParentsNeedResolving - lenCurItems + 1
curItems = nil // free the memory
curItem.ParentNo = lowestParentNo
// Now check if we can remove the parent completely
for curItem.Child.IdenticalToChild && curItem.Child.MergedChildren == nil && curItem.Child.numParentsNeedResolving == 1 {
oldChild := curItem.Child
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 {
newNeedsMap = append(newNeedsMap, cur[c])
newPos := len(newNeedsMap) - 1
for newPos > n {
newNeedsMap[newPos-1], newNeedsMap[newPos] = newNeedsMap[newPos], newNeedsMap[newPos-1]
newPos--
}
}
}
}
curItem.NeedsMap = newNeedsMap
curItem.IdenticalToChild = false
curItem.Child = nil
curItems = nil // free the memory
}
parents, err := parentsContainingPath(curItem.path, curItem.Commit)
if err != nil {
return false, err
}
anyPushed := false
for parnetNo, prev := range parents {
currentHash, err := blobHash(curItem.path, curItem.Commit)
if err != nil {
return false, err
}
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 := rev.File(b.path)
file, err := prev.Commit.File(prev.Path)
if err != nil {
return nil
return false, err
}
b.data[i], err = file.Contents()
prevContents, err := file.Contents()
if err != nil {
return err
return false, err
}
nLines := countLines(b.data[i])
// create a node for each line
b.graph[i] = make([]*object.Commit, nLines)
// assign a commit to each node
// if this is the first revision, then the node is assigned to
// this first commit.
if i == 0 {
for j := 0; j < nLines; j++ {
b.graph[i][j] = b.revs[i]
hunks := diff.Do(prevContents, curItem.Contents)
prevl := -1
curl := -1
need := 0
getFromParent := make([]lineMap, 0)
out:
for h := range hunks {
hLines := countLines(hunks[h].Text)
for hl := 0; hl < hLines; hl++ {
switch {
case hunks[h].Type == diffmatchpatch.DiffEqual:
prevl++
curl++
if curl == curItem.NeedsMap[need].Cur {
// add to needs
getFromParent = append(getFromParent, lineMap{curl, prevl, nil, -1})
// move to next need
need++
if need >= len(curItem.NeedsMap) {
break out
}
}
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")
}
}
} else {
// if this is not the first commit, then assign to the old
// commit or to the new one, depending on what the diff
// says.
b.assignOrigin(i, i-1)
}
if len(getFromParent) > 0 {
b.q.Push(&queueItem{
curItem,
nil,
prev.Commit,
prev.Path,
prevContents,
getFromParent,
0,
false,
parnetNo,
})
curItem.numParentsNeedResolving++
anyPushed = true
}
}
return nil
}
// sliceGraph returns a slice of commits (one per line) for a particular
// revision of a file (0=first revision).
func (b *blame) sliceGraph(i int) []*object.Commit {
fVs := b.graph[i]
result := make([]*object.Commit, 0, len(fVs))
for _, v := range fVs {
c := *v
result = append(result, &c)
curItem.Contents = "" // no longer need, free the memory
if !anyPushed {
return finishNeeds(curItem)
}
return result
return false, nil
}
// Assigns origin to vertexes in current (c) rev from data in its previous (p)
// revision
func (b *blame) assignOrigin(c, p int) {
// assign origin based on diff info
hunks := diff.Do(b.data[p], b.data[c])
sl := -1 // source line
dl := -1 // destination line
for h := range hunks {
hLines := countLines(hunks[h].Text)
for hl := 0; hl < hLines; hl++ {
switch {
case hunks[h].Type == 0:
sl++
dl++
b.graph[c][dl] = b.graph[p][sl]
case hunks[h].Type == 1:
dl++
b.graph[c][dl] = b.revs[c]
case hunks[h].Type == -1:
sl++
default:
panic("unreachable")
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
}
// GoString prints the results of a Blame using git-blame's style.
func (b *blame) GoString() string {
// String prints the results of a Blame using git-blame's style.
func (b BlameResult) String() string {
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
mlnl := len(strconv.Itoa(len(lines)))
mlnl := len(strconv.Itoa(len(b.Lines)))
// max author length
mal := b.maxAuthorLength()
format := fmt.Sprintf("%%s (%%-%ds %%%dd) %%s\n",
mal, mlnl)
format := fmt.Sprintf("%%s (%%-%ds %%s %%%dd) %%s\n", mal, mlnl)
fVs := b.graph[len(b.graph)-1]
for ln, v := range fVs {
fmt.Fprintf(&buf, format, v.Hash.String()[:8],
prettyPrintAuthor(fVs[ln]), ln+1, lines[ln])
for ln := range b.Lines {
_, _ = fmt.Fprintf(&buf, format, b.Lines[ln].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)
}
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
// to print the longest author name in the blame of a file.
func (b *blame) maxAuthorLength() int {
memo := make(map[plumbing.Hash]struct{}, len(b.graph)-1)
fVs := b.graph[len(b.graph)-1]
func (b BlameResult) maxAuthorLength() int {
m := 0
for ln := range fVs {
if _, ok := memo[fVs[ln].Hash]; ok {
continue
}
memo[fVs[ln].Hash] = struct{}{}
m = max(m, utf8.RuneCountInString(prettyPrintAuthor(fVs[ln])))
for ln := range b.Lines {
m = max(m, utf8.RuneCountInString(b.Lines[ln].AuthorName))
}
return m
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
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
}

View 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
}

View File

@ -62,6 +62,9 @@ type CloneOptions struct {
// within, using their default settings. This option is ignored if the
// cloned repository does not have a worktree.
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
// stored, if nil nothing is stored and the capability (if supported)
// no-progress, is sent to the server to avoid send this information.

View File

@ -8,6 +8,7 @@ import (
"strings"
"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"
gioutil "github.com/go-git/go-git/v5/utils/ioutil"
)
@ -25,6 +26,9 @@ const (
// readIgnoreFile reads a specific git ignore file.
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)...))
if err == nil {
defer f.Close()

View File

@ -39,6 +39,8 @@ type pattern struct {
// ParsePattern parses a gitignore pattern string into the Pattern structure.
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}
if strings.HasPrefix(p, inclusionPrefix) {

View File

@ -376,6 +376,17 @@ func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
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 {
var output []string
for _, line := range strings.Split(t, "\n") {

View File

@ -133,6 +133,7 @@ func decodeFirstHash(p *advRefsDecoder) decoderStateFn {
return nil
}
// TODO: Use object-format (when available) for hash size. Git 2.41+
if len(p.line) < hashSize {
p.error("cannot read hash, pkt-line too short")
return nil

View File

@ -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
// length is zero
// IsEmpty returns whether a request is empty - it is empty if Haves are contained
// in the Wants, or if Wants length is zero, and we don't have any shallows
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 {

View File

@ -15,10 +15,11 @@ const (
symrefPrefix = "ref: "
)
// RefRevParseRules are a set of rules to parse references into short names.
// These are the same rules as used by git in shorten_unambiguous_ref.
// 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 and expand_ref.
// See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417
var RefRevParseRules = []string{
"%s",
"refs/%s",
"refs/tags/%s",
"refs/heads/%s",
@ -113,7 +114,7 @@ func (r ReferenceName) String() string {
func (r ReferenceName) Short() string {
s := string(r)
res := s
for _, format := range RefRevParseRules {
for _, format := range RefRevParseRules[1:] {
_, err := fmt.Sscanf(s, format, &res)
if err == nil {
continue

View File

@ -73,6 +73,17 @@ func advertisedReferences(ctx context.Context, s *session, serviceName string) (
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)
s.advRefs = ar

View File

@ -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
// 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) {
if req.IsEmpty() && len(req.Shallows) == 0 {
if req.IsEmpty() {
return nil, transport.ErrEmptyUploadPackRequest
}

View File

@ -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")
}
}

View File

@ -224,11 +224,13 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) {
return err
}
if err = rs.Error(); err != nil {
return err
if rs != nil {
if err = rs.Error(); err != nil {
return err
}
}
return r.updateRemoteReferenceStorage(req, rs)
return r.updateRemoteReferenceStorage(req)
}
func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool {
@ -347,7 +349,6 @@ func (r *Remote) newReferenceUpdateRequest(
func (r *Remote) updateRemoteReferenceStorage(
req *packp.ReferenceUpdateRequest,
result *packp.ReportStatus,
) error {
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
}
refs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
refs, specToRefs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
if err != nil {
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 {
req.Haves, err = getHaves(localRefs, remoteRefs, r.s)
req.Haves, err = getHaves(localRefs, remoteRefs, r.s, o.Depth)
if err != nil {
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 {
return nil, err
}
@ -837,6 +838,7 @@ func getHavesFromRef(
remoteRefs map[plumbing.Hash]bool,
s storage.Storer,
haves map[plumbing.Hash]bool,
depth int,
) error {
h := ref.Hash()
if haves[h] {
@ -862,7 +864,13 @@ func getHavesFromRef(
// commits from the history of each ref.
walker := object.NewCommitPreorderIter(commit, haves, nil)
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
toVisit--
// If toVisit starts out at 0 (indicating there is no
@ -873,12 +881,15 @@ func getHavesFromRef(
}
return nil
})
return nil
}
func getHaves(
localRefs []*plumbing.Reference,
remoteRefStorer storer.ReferenceStorer,
s storage.Storer,
depth int,
) ([]plumbing.Hash, error) {
haves := map[plumbing.Hash]bool{}
@ -899,7 +910,7 @@ func getHaves(
continue
}
err = getHavesFromRef(ref, remoteRefs, s, haves)
err = getHavesFromRef(ref, remoteRefs, s, haves, depth)
if err != nil {
return nil, err
}
@ -919,42 +930,41 @@ func calculateRefs(
spec []config.RefSpec,
remoteRefs storer.ReferenceStorer,
tagMode TagMode,
) (memory.ReferenceStorage, error) {
) (memory.ReferenceStorage, [][]*plumbing.Reference, error) {
if tagMode == AllTags {
spec = append(spec, refspecAllTags)
}
refs := make(memory.ReferenceStorage)
for _, s := range spec {
if err := doCalculateRefs(s, remoteRefs, refs); err != nil {
return nil, err
// list of references matched for each spec
specToRefs := make([][]*plumbing.Reference, len(spec))
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(
s config.RefSpec,
remoteRefs storer.ReferenceStorer,
refs memory.ReferenceStorage,
) error {
iter, err := remoteRefs.IterReferences()
if err != nil {
return err
}
) ([]*plumbing.Reference, error) {
var refList []*plumbing.Reference
if s.IsExactSHA1() {
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
err = iter.ForEach(func(ref *plumbing.Reference) error {
if !s.Match(ref.Name()) {
return nil
}
onMatched := func(ref *plumbing.Reference) error {
if ref.Type() == plumbing.SymbolicReference {
target, err := storer.ResolveReference(remoteRefs, ref.Name())
if err != nil {
@ -969,28 +979,47 @@ func doCalculateRefs(
}
matched = true
if err := refs.SetReference(ref); err != nil {
return err
}
if !s.IsWildcard() {
return storer.ErrStop
}
return nil
})
if !matched && !s.IsWildcard() {
return NoMatchingRefSpecError{refSpec: s}
refList = append(refList, ref)
return refs.SetReference(ref)
}
return err
var ret error
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 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() {
return nil, NoMatchingRefSpecError{refSpec: s}
}
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
if s, _ := localStorer.Shallow(); len(s) > 0 {
shallow = true
if depth != 1 {
if s, _ := localStorer.Shallow(); len(s) > 0 {
shallow = true
}
}
wants := map[plumbing.Hash]bool{}
@ -1144,27 +1173,28 @@ func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.P
func (r *Remote) updateLocalReferenceStorage(
specs []config.RefSpec,
fetchedRefs, remoteRefs memory.ReferenceStorage,
specToRefs [][]*plumbing.Reference,
tagMode TagMode,
force bool,
) (updated bool, err error) {
isWildcard := true
forceNeeded := false
for _, spec := range specs {
for i, spec := range specs {
if !spec.IsWildcard() {
isWildcard = false
}
for _, ref := range fetchedRefs {
if !spec.Match(ref.Name()) && !spec.IsExactSHA1() {
continue
}
for _, ref := range specToRefs[i] {
if ref.Type() != plumbing.HashReference {
continue
}
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)
new := plumbing.NewHashReference(localName, ref.Hash())

View File

@ -19,6 +19,7 @@ import (
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-billy/v5/util"
"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/plumbing"
"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) {
path, err = path_util.ReplaceTildeWithHome(path)
if err != nil {
return nil, nil, err
}
if path, err = filepath.Abs(path); err != nil {
return nil, nil, err
}
@ -916,7 +922,13 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
if o.RecurseSubmodules != NoRecurseSubmodules {
if err := w.updateSubmodules(&SubmoduleUpdateOptions{
RecurseSubmodules: o.RecurseSubmodules,
Auth: o.Auth,
Depth: func() int {
if o.ShallowSubmodules {
return 1
}
return 0
}(),
Auth: o.Auth,
}); err != nil {
return err
}
@ -967,7 +979,6 @@ func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec {
case o.SingleBranch && o.ReferenceName == plumbing.HEAD:
return []config.RefSpec{
config.RefSpec(fmt.Sprintf(refspecSingleBranchHEAD, o.RemoteName)),
config.RefSpec(fmt.Sprintf(refspecSingleBranch, plumbing.Master.Short(), o.RemoteName)),
}
case o.SingleBranch:
return []config.RefSpec{
@ -1029,21 +1040,9 @@ func (r *Repository) fetchAndUpdateReferences(
return nil, err
}
var resolvedRef *plumbing.Reference
// 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
}
}
resolvedRef, err := expand_ref(remoteRefs, ref)
if err != nil {
return nil, rawRefError
return nil, err
}
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
}
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
// 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))...)
for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) {
ref, err := storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef)))
if err == nil {
tryHashes = append(tryHashes, ref.Hash())
break
}
ref, err := expand_ref(r.Storer, plumbing.ReferenceName(revisionRef))
if err == nil {
tryHashes = append(tryHashes, ref.Hash())
}
// in ambiguous cases, `git rev-parse` will emit a warning, but

View File

@ -582,7 +582,9 @@ func (d *DotGit) hasIncomingObjects() bool {
directoryContents, err := d.fs.ReadDir(objectsPath)
if err == nil {
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()
}
}

View File

@ -5,13 +5,13 @@ import (
"context"
"errors"
"fmt"
"net/url"
"path"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5/config"
"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/transport"
)
var (
@ -133,29 +133,29 @@ func (s *Submodule) Repository() (*Repository, error) {
return nil, err
}
moduleURL, err := url.Parse(s.c.URL)
moduleEndpoint, err := transport.NewEndpoint(s.c.URL)
if err != nil {
return nil, err
}
if !path.IsAbs(moduleURL.Path) {
if !path.IsAbs(moduleEndpoint.Path) && moduleEndpoint.Protocol == "file" {
remotes, err := s.w.r.Remotes()
if err != nil {
return nil, err
}
rootURL, err := url.Parse(remotes[0].c.URLs[0])
rootEndpoint, err := transport.NewEndpoint(remotes[0].c.URLs[0])
if err != nil {
return nil, err
}
rootURL.Path = path.Join(rootURL.Path, moduleURL.Path)
*moduleURL = *rootURL
rootEndpoint.Path = path.Join(rootEndpoint.Path, moduleEndpoint.Path)
*moduleEndpoint = *rootEndpoint
}
_, err = r.CreateRemote(&config.RemoteConfig{
Name: DefaultRemoteName,
URLs: []string{moduleURL.String()},
URLs: []string{moduleEndpoint.String()},
})
return r, err

View File

@ -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 {
if !isPathInDirectory(name, filepath.ToSlash(filepath.Clean(directory))) {
if !isPathInDirectory(name, directory) {
continue
}
@ -292,32 +294,14 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string,
return
}
if !added && a {
added = true
}
added = added || a
}
return
}
func isPathInDirectory(path, directory string) bool {
ps := strings.Split(path, "/")
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
return directory == "." || strings.HasPrefix(path, directory+"/")
}
// AddWithOptions file contents to the index, updates the index using the

3
vendor/modules.txt vendored
View File

@ -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/osfs
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
github.com/go-git/go-git/v5
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/url
github.com/go-git/go-git/v5/plumbing