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/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
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/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=
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
"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
|
||||
}
|
||||
|
|
|
|||
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
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
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
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue