aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--_examples/README.md1
-rw-r--r--_examples/find-if-any-tag-point-head/main.go38
-rw-r--r--_examples/remotes/main.go2
-rw-r--r--common_test.go12
-rw-r--r--config/branch.go33
-rw-r--r--config/config.go3
-rw-r--r--config/config_test.go12
-rw-r--r--go.mod7
-rw-r--r--go.sum31
-rw-r--r--object_walker.go4
-rw-r--r--options.go42
-rw-r--r--plumbing/format/commitgraph/file.go6
-rw-r--r--plumbing/format/gitattributes/attributes.go2
-rw-r--r--plumbing/format/gitignore/dir.go25
-rw-r--r--plumbing/format/gitignore/dir_test.go17
-rw-r--r--plumbing/format/idxfile/decoder.go4
-rw-r--r--plumbing/format/index/encoder.go34
-rw-r--r--plumbing/format/index/encoder_test.go26
-rw-r--r--plumbing/format/index/index.go18
-rw-r--r--plumbing/format/packfile/fsobject.go15
-rw-r--r--plumbing/format/packfile/packfile_test.go17
-rw-r--r--plumbing/format/packfile/parser.go1
-rw-r--r--plumbing/object/change_adaptor.go4
-rw-r--r--plumbing/object/patch.go4
-rw-r--r--plumbing/object/treenoder.go4
-rw-r--r--plumbing/protocol/packp/common.go1
-rw-r--r--plumbing/protocol/packp/updreq.go12
-rw-r--r--plumbing/protocol/packp/updreq_encode.go22
-rw-r--r--plumbing/protocol/packp/updreq_encode_test.go49
-rw-r--r--plumbing/protocol/packp/uppackresp.go1
-rw-r--r--plumbing/revlist/revlist_test.go6
-rw-r--r--plumbing/transport/client/client_test.go5
-rw-r--r--plumbing/transport/internal/common/common.go5
-rw-r--r--plumbing/transport/ssh/common_test.go3
-rw-r--r--prune.go2
-rw-r--r--remote.go188
-rw-r--r--remote_test.go293
-rw-r--r--repository.go4
-rw-r--r--repository_test.go33
-rw-r--r--storage/filesystem/dotgit/dotgit_test.go9
-rw-r--r--storage/filesystem/dotgit/reader.go2
-rw-r--r--storage/filesystem/object_test.go6
-rw-r--r--storage/memory/storage.go2
-rw-r--r--storage/transactional/config_test.go2
-rw-r--r--storage/transactional/reference.go4
-rw-r--r--utils/merkletrie/difftree.go29
-rw-r--r--utils/merkletrie/filesystem/node.go4
-rw-r--r--utils/merkletrie/index/node.go7
-rw-r--r--utils/merkletrie/internal/fsnoder/dir.go4
-rw-r--r--utils/merkletrie/internal/fsnoder/file.go4
-rw-r--r--utils/merkletrie/noder/noder.go1
-rw-r--r--utils/merkletrie/noder/noder_test.go15
-rw-r--r--utils/merkletrie/noder/path.go8
-rw-r--r--worktree.go26
-rw-r--r--worktree_commit_test.go5
-rw-r--r--worktree_test.go35
56 files changed, 958 insertions, 191 deletions
diff --git a/_examples/README.md b/_examples/README.md
index 92b9d4d..3a4c539 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -19,6 +19,7 @@ Here you can find a list of annotated _go-git_ examples:
- [branch](branch/main.go) - How to create and remove branches or any other kind of reference.
- [tag](tag/main.go) - List/print repository tags.
- [tag create and push](tag-create-push/main.go) - Create and push a new tag.
+- [tag find if head is tagged](find-if-any-tag-point-head/main.go) - Find if `HEAD` is tagged.
- [remotes](remotes/main.go) - Working with remotes: adding, removing, etc.
- [progress](progress/main.go) - Printing the progress information from the sideband.
- [revision](revision/main.go) - Solve a revision into a commit.
diff --git a/_examples/find-if-any-tag-point-head/main.go b/_examples/find-if-any-tag-point-head/main.go
new file mode 100644
index 0000000..834aea2
--- /dev/null
+++ b/_examples/find-if-any-tag-point-head/main.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/go-git/go-git/v5"
+ . "github.com/go-git/go-git/v5/_examples"
+ "github.com/go-git/go-git/v5/plumbing"
+)
+
+// Basic example of how to find if HEAD is tagged.
+func main() {
+ CheckArgs("<path>")
+ path := os.Args[1]
+
+ // We instantiate a new repository targeting the given path (the .git folder)
+ r, err := git.PlainOpen(path)
+ CheckIfError(err)
+
+ // Get HEAD reference to use for comparison later on.
+ ref, err := r.Head()
+ CheckIfError(err)
+
+ tags, err := r.Tags()
+ CheckIfError(err)
+
+ // List all tags, both lightweight tags and annotated tags and see if some tag points to HEAD reference.
+ err = tags.ForEach(func(t *plumbing.Reference) error {
+ // This technique should work for both lightweight and annotated tags.
+ revHash, err := r.ResolveRevision(plumbing.Revision(t.Name()))
+ CheckIfError(err)
+ if *revHash == ref.Hash() {
+ fmt.Printf("Found tag %s with hash %s pointing to HEAD %s\n", t.Name().Short(), revHash, ref.Hash())
+ }
+ return nil
+ })
+}
diff --git a/_examples/remotes/main.go b/_examples/remotes/main.go
index b1a91a9..d09957e 100644
--- a/_examples/remotes/main.go
+++ b/_examples/remotes/main.go
@@ -33,7 +33,7 @@ func main() {
CheckIfError(err)
// List remotes from a repository
- Info("git remotes -v")
+ Info("git remote -v")
list, err := r.Remotes()
CheckIfError(err)
diff --git a/common_test.go b/common_test.go
index 5f5bc4c..c0c4009 100644
--- a/common_test.go
+++ b/common_test.go
@@ -7,7 +7,6 @@ import (
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/format/packfile"
- "github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/storage/filesystem"
"github.com/go-git/go-git/v5/storage/memory"
@@ -25,8 +24,7 @@ type BaseSuite struct {
fixtures.Suite
Repository *Repository
- backupProtocol transport.Transport
- cache map[string]*Repository
+ cache map[string]*Repository
}
func (s *BaseSuite) SetUpSuite(c *C) {
@@ -198,3 +196,11 @@ func AssertReferences(c *C, r *Repository, expected map[string]string) {
c.Assert(obtained, DeepEquals, expected)
}
}
+
+func AssertReferencesMissing(c *C, r *Repository, expected []string) {
+ for _, name := range expected {
+ _, err := r.Reference(plumbing.ReferenceName(name), false)
+ c.Assert(err, NotNil)
+ c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
+ }
+}
diff --git a/config/branch.go b/config/branch.go
index fe86cf5..652270a 100644
--- a/config/branch.go
+++ b/config/branch.go
@@ -2,6 +2,7 @@ package config
import (
"errors"
+ "strings"
"github.com/go-git/go-git/v5/plumbing"
format "github.com/go-git/go-git/v5/plumbing/format/config"
@@ -26,6 +27,12 @@ type Branch struct {
// "true" and "interactive". "false" is undocumented and
// typically represented by the non-existence of this field
Rebase string
+ // Description explains what the branch is for.
+ // Multi-line explanations may be used.
+ //
+ // Original git command to edit:
+ // git branch --edit-description
+ Description string
raw *format.Subsection
}
@@ -75,9 +82,27 @@ func (b *Branch) marshal() *format.Subsection {
b.raw.SetOption(rebaseKey, b.Rebase)
}
+ if b.Description == "" {
+ b.raw.RemoveOption(descriptionKey)
+ } else {
+ desc := quoteDescription(b.Description)
+ b.raw.SetOption(descriptionKey, desc)
+ }
+
return b.raw
}
+// hack to trigger conditional quoting in the
+// plumbing/format/config/Encoder.encodeOptions
+//
+// Current Encoder implementation uses Go %q format if value contains a backslash character,
+// which is not consistent with reference git implementation.
+// git just replaces newline characters with \n, while Encoder prints them directly.
+// Until value quoting fix, we should escape description value by replacing newline characters with \n.
+func quoteDescription(desc string) string {
+ return strings.ReplaceAll(desc, "\n", `\n`)
+}
+
func (b *Branch) unmarshal(s *format.Subsection) error {
b.raw = s
@@ -85,6 +110,14 @@ func (b *Branch) unmarshal(s *format.Subsection) error {
b.Remote = b.raw.Options.Get(remoteSection)
b.Merge = plumbing.ReferenceName(b.raw.Options.Get(mergeKey))
b.Rebase = b.raw.Options.Get(rebaseKey)
+ b.Description = unquoteDescription(b.raw.Options.Get(descriptionKey))
return b.Validate()
}
+
+// hack to enable conditional quoting in the
+// plumbing/format/config/Encoder.encodeOptions
+// goto quoteDescription for details.
+func unquoteDescription(desc string) string {
+ return strings.ReplaceAll(desc, `\n`, "\n")
+}
diff --git a/config/config.go b/config/config.go
index 1aee25a..2a196d0 100644
--- a/config/config.go
+++ b/config/config.go
@@ -150,7 +150,7 @@ func ReadConfig(r io.Reader) (*Config, error) {
// config file to the given scope, a empty one is returned.
func LoadConfig(scope Scope) (*Config, error) {
if scope == LocalScope {
- return nil, fmt.Errorf("LocalScope should be read from the a ConfigStorer.")
+ return nil, fmt.Errorf("LocalScope should be read from the a ConfigStorer")
}
files, err := Paths(scope)
@@ -247,6 +247,7 @@ const (
rebaseKey = "rebase"
nameKey = "name"
emailKey = "email"
+ descriptionKey = "description"
defaultBranchKey = "defaultBranch"
// DefaultPackWindow holds the number of previous objects used to
diff --git a/config/config_test.go b/config/config_test.go
index 6f0242d..7e9483f 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -50,6 +50,7 @@ func (s *ConfigSuite) TestUnmarshal(c *C) {
[branch "master"]
remote = origin
merge = refs/heads/master
+ description = "Add support for branch description.\\n\\nEdit branch description: git branch --edit-description\\n"
[init]
defaultBranch = main
[url "ssh://git@github.com/"]
@@ -86,6 +87,7 @@ func (s *ConfigSuite) TestUnmarshal(c *C) {
c.Assert(cfg.Submodules["qux"].Branch, Equals, "bar")
c.Assert(cfg.Branches["master"].Remote, Equals, "origin")
c.Assert(cfg.Branches["master"].Merge, Equals, plumbing.ReferenceName("refs/heads/master"))
+ c.Assert(cfg.Branches["master"].Description, Equals, "Add support for branch description.\n\nEdit branch description: git branch --edit-description\n")
c.Assert(cfg.Init.DefaultBranch, Equals, "main")
}
@@ -111,6 +113,7 @@ func (s *ConfigSuite) TestMarshal(c *C) {
[branch "master"]
remote = origin
merge = refs/heads/master
+ description = "Add support for branch description.\\n\\nEdit branch description: git branch --edit-description\\n"
[url "ssh://git@github.com/"]
insteadOf = https://github.com/
[init]
@@ -149,9 +152,10 @@ func (s *ConfigSuite) TestMarshal(c *C) {
}
cfg.Branches["master"] = &Branch{
- Name: "master",
- Remote: "origin",
- Merge: "refs/heads/master",
+ Name: "master",
+ Remote: "origin",
+ Merge: "refs/heads/master",
+ Description: "Add support for branch description.\n\nEdit branch description: git branch --edit-description\n",
}
cfg.URLs["ssh://git@github.com/"] = &URL{
@@ -361,7 +365,9 @@ func (s *ConfigSuite) TestRemoveUrlOptions(c *C) {
cfg.Remotes["alt"].URLs = []string{}
buf, err = cfg.Marshal()
+ c.Assert(err, IsNil)
if strings.Contains(string(buf), "url") {
c.Fatal("conifg should not contain any url sections")
}
+ c.Assert(err, IsNil)
}
diff --git a/go.mod b/go.mod
index 402613f..ccb8fac 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,6 @@
module github.com/go-git/go-git/v5
require (
- github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
github.com/acomagu/bufpipe v1.0.3
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
@@ -19,10 +18,10 @@ require (
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351
github.com/mitchellh/go-homedir v1.1.0
github.com/sergi/go-diff v1.1.0
- github.com/xanzy/ssh-agent v0.3.0
- golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
+ github.com/xanzy/ssh-agent v0.3.1
+ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/net v0.0.0-20210326060303-6b1517762897
- golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79
+ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/text v0.3.3
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gopkg.in/warnings.v0 v0.1.2 // indirect
diff --git a/go.sum b/go.sum
index 9227247..ab212c5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,5 @@
-github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
-github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
-github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
+github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
+github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
@@ -22,8 +21,6 @@ github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-github.com/go-git/go-billy/v5 v5.3.0 h1:KZL1OFdS+afiIjN4hr/zpj5cEtC0OJhbmTA18PsBb8c=
-github.com/go-git/go-billy/v5 v5.3.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
@@ -38,7 +35,6 @@ github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LF
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -51,38 +47,35 @@ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlW
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
-github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
-golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
+github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
-golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 h1:RX8C8PRZc2hTIod4ds8ij+/4RQX3AqhYj3uOHmyaz4E=
-golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
diff --git a/object_walker.go b/object_walker.go
index 3fcdd29..3a537bd 100644
--- a/object_walker.go
+++ b/object_walker.go
@@ -60,7 +60,7 @@ func (p *objectWalker) walkObjectTree(hash plumbing.Hash) error {
// Fetch the object.
obj, err := object.GetObject(p.Storer, hash)
if err != nil {
- return fmt.Errorf("Getting object %s failed: %v", hash, err)
+ return fmt.Errorf("getting object %s failed: %v", hash, err)
}
// Walk all children depending on object type.
switch obj := obj.(type) {
@@ -98,7 +98,7 @@ func (p *objectWalker) walkObjectTree(hash plumbing.Hash) error {
return p.walkObjectTree(obj.Target)
default:
// Error out on unhandled object types.
- return fmt.Errorf("Unknown object %X %s %T\n", obj.ID(), obj.Type(), obj)
+ return fmt.Errorf("unknown object %X %s %T", obj.ID(), obj.Type(), obj)
}
return nil
}
diff --git a/options.go b/options.go
index 7068796..d6f0e9f 100644
--- a/options.go
+++ b/options.go
@@ -91,6 +91,8 @@ func (o *CloneOptions) Validate() error {
type PullOptions struct {
// Name of the remote to be pulled. If empty, uses the default.
RemoteName string
+ // RemoteURL overrides the remote repo address with a custom URL
+ RemoteURL string
// Remote branch to clone. If empty, uses HEAD.
ReferenceName plumbing.ReferenceName
// Fetch only ReferenceName if true.
@@ -147,7 +149,9 @@ const (
type FetchOptions struct {
// Name of the remote to fetch from. Defaults to origin.
RemoteName string
- RefSpecs []config.RefSpec
+ // RemoteURL overrides the remote repo address with a custom URL
+ RemoteURL string
+ RefSpecs []config.RefSpec
// Depth limit fetching to the specified number of commits from the tip of
// each remote branch history.
Depth int
@@ -192,8 +196,16 @@ func (o *FetchOptions) Validate() error {
type PushOptions struct {
// RemoteName is the name of the remote to be pushed to.
RemoteName string
- // RefSpecs specify what destination ref to update with what source
- // object. A refspec with empty src can be used to delete a reference.
+ // RemoteURL overrides the remote repo address with a custom URL
+ RemoteURL string
+ // RefSpecs specify what destination ref to update with what source object.
+ //
+ // The format of a <refspec> parameter is an optional plus +, followed by
+ // the source object <src>, followed by a colon :, followed by the destination ref <dst>.
+ // The <src> is often the name of the branch you would want to push, but it can be a SHA-1.
+ // The <dst> tells which ref on the remote side is updated with this push.
+ //
+ // A refspec with empty src can be used to delete a reference.
RefSpecs []config.RefSpec
// Auth credentials, if required, to use with the remote repository.
Auth transport.AuthMethod
@@ -213,6 +225,28 @@ type PushOptions struct {
// RequireRemoteRefs only allows a remote ref to be updated if its current
// value is the one specified here.
RequireRemoteRefs []config.RefSpec
+ // FollowTags will send any annotated tags with a commit target reachable from
+ // the refs already being pushed
+ FollowTags bool
+ // ForceWithLease allows a force push as long as the remote ref adheres to a "lease"
+ ForceWithLease *ForceWithLease
+ // PushOptions sets options to be transferred to the server during push.
+ Options map[string]string
+ // Atomic sets option to be an atomic push
+ Atomic bool
+}
+
+// ForceWithLease sets fields on the lease
+// If neither RefName nor Hash are set, ForceWithLease protects
+// all refs in the refspec by ensuring the ref of the remote in the local repsitory
+// matches the one in the ref advertisement.
+type ForceWithLease struct {
+ // RefName, when set will protect the ref by ensuring it matches the
+ // hash in the ref advertisement.
+ RefName plumbing.ReferenceName
+ // Hash is the expected object id of RefName. The push will be rejected unless this
+ // matches the corresponding object id of RefName in the refs advertisement.
+ Hash plumbing.Hash
}
// Validate validates the fields and sets the default values.
@@ -274,6 +308,8 @@ type CheckoutOptions struct {
// target branch. Force and Keep are mutually exclusive, should not be both
// set to true.
Keep bool
+ // SparseCheckoutDirectories
+ SparseCheckoutDirectories []string
}
// Validate validates the fields and sets the default values.
diff --git a/plumbing/format/commitgraph/file.go b/plumbing/format/commitgraph/file.go
index 0ce7198..1d25238 100644
--- a/plumbing/format/commitgraph/file.go
+++ b/plumbing/format/commitgraph/file.go
@@ -14,14 +14,14 @@ import (
var (
// ErrUnsupportedVersion is returned by OpenFileIndex when the commit graph
// file version is not supported.
- ErrUnsupportedVersion = errors.New("Unsupported version")
+ ErrUnsupportedVersion = errors.New("unsupported version")
// ErrUnsupportedHash is returned by OpenFileIndex when the commit graph
// hash function is not supported. Currently only SHA-1 is defined and
// supported
- ErrUnsupportedHash = errors.New("Unsupported hash algorithm")
+ ErrUnsupportedHash = errors.New("unsupported hash algorithm")
// ErrMalformedCommitGraphFile is returned by OpenFileIndex when the commit
// graph file is corrupted.
- ErrMalformedCommitGraphFile = errors.New("Malformed commit graph file")
+ ErrMalformedCommitGraphFile = errors.New("malformed commit graph file")
commitFileSignature = []byte{'C', 'G', 'P', 'H'}
oidFanoutSignature = []byte{'O', 'I', 'D', 'F'}
diff --git a/plumbing/format/gitattributes/attributes.go b/plumbing/format/gitattributes/attributes.go
index d13c2a9..329e667 100644
--- a/plumbing/format/gitattributes/attributes.go
+++ b/plumbing/format/gitattributes/attributes.go
@@ -15,7 +15,7 @@ const (
var (
ErrMacroNotAllowed = errors.New("macro not allowed")
- ErrInvalidAttributeName = errors.New("Invalid attribute name")
+ ErrInvalidAttributeName = errors.New("invalid attribute name")
)
type MatchAttribute struct {
diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go
index 7cea50c..15bc9c7 100644
--- a/plumbing/format/gitignore/dir.go
+++ b/plumbing/format/gitignore/dir.go
@@ -13,13 +13,14 @@ import (
)
const (
- commentPrefix = "#"
- coreSection = "core"
- excludesfile = "excludesfile"
- gitDir = ".git"
- gitignoreFile = ".gitignore"
- gitconfigFile = ".gitconfig"
- systemFile = "/etc/gitconfig"
+ commentPrefix = "#"
+ coreSection = "core"
+ excludesfile = "excludesfile"
+ gitDir = ".git"
+ gitignoreFile = ".gitignore"
+ gitconfigFile = ".gitconfig"
+ systemFile = "/etc/gitconfig"
+ infoExcludeFile = gitDir + "/info/exclude"
)
// readIgnoreFile reads a specific git ignore file.
@@ -42,10 +43,14 @@ func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps [
return
}
-// ReadPatterns reads gitignore patterns recursively traversing through the directory
-// structure. The result is in the ascending order of priority (last higher).
+// ReadPatterns reads the .git/info/exclude and then the gitignore patterns
+// recursively traversing through the directory structure. The result is in
+// the ascending order of priority (last higher).
func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) {
- ps, _ = readIgnoreFile(fs, path, gitignoreFile)
+ ps, _ = readIgnoreFile(fs, path, infoExcludeFile)
+
+ subps, _ := readIgnoreFile(fs, path, gitignoreFile)
+ ps = append(ps, subps...)
var fis []os.FileInfo
fis, err = fs.ReadDir(fs.Join(path...))
diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go
index 94ed7be..facc36d 100644
--- a/plumbing/format/gitignore/dir_test.go
+++ b/plumbing/format/gitignore/dir_test.go
@@ -24,7 +24,17 @@ var _ = Suite(&MatcherSuite{})
func (s *MatcherSuite) SetUpTest(c *C) {
// setup generic git repository root
fs := memfs.New()
- f, err := fs.Create(".gitignore")
+
+ err := fs.MkdirAll(".git/info", os.ModePerm)
+ c.Assert(err, IsNil)
+ f, err := fs.Create(".git/info/exclude")
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("exclude.crlf\r\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ f, err = fs.Create(".gitignore")
c.Assert(err, IsNil)
_, err = f.Write([]byte("vendor/g*/\n"))
c.Assert(err, IsNil)
@@ -44,6 +54,8 @@ func (s *MatcherSuite) SetUpTest(c *C) {
err = fs.MkdirAll("another", os.ModePerm)
c.Assert(err, IsNil)
+ err = fs.MkdirAll("exclude.crlf", os.ModePerm)
+ c.Assert(err, IsNil)
err = fs.MkdirAll("ignore.crlf", os.ModePerm)
c.Assert(err, IsNil)
err = fs.MkdirAll("vendor/github.com", os.ModePerm)
@@ -173,9 +185,10 @@ func (s *MatcherSuite) SetUpTest(c *C) {
func (s *MatcherSuite) TestDir_ReadPatterns(c *C) {
ps, err := ReadPatterns(s.GFS, nil)
c.Assert(err, IsNil)
- c.Assert(ps, HasLen, 3)
+ c.Assert(ps, HasLen, 4)
m := NewMatcher(ps)
+ c.Assert(m.Match([]string{"exclude.crlf"}, true), Equals, true)
c.Assert(m.Match([]string{"ignore.crlf"}, true), Equals, true)
c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true)
c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false)
diff --git a/plumbing/format/idxfile/decoder.go b/plumbing/format/idxfile/decoder.go
index 7768bd6..51a3904 100644
--- a/plumbing/format/idxfile/decoder.go
+++ b/plumbing/format/idxfile/decoder.go
@@ -12,9 +12,9 @@ import (
var (
// ErrUnsupportedVersion is returned by Decode when the idx file version
// is not supported.
- ErrUnsupportedVersion = errors.New("Unsupported version")
+ ErrUnsupportedVersion = errors.New("unsupported version")
// ErrMalformedIdxFile is returned by Decode when the idx file is corrupted.
- ErrMalformedIdxFile = errors.New("Malformed IDX file")
+ ErrMalformedIdxFile = errors.New("malformed IDX file")
)
const (
diff --git a/plumbing/format/index/encoder.go b/plumbing/format/index/encoder.go
index 00d4e7a..2c94d93 100644
--- a/plumbing/format/index/encoder.go
+++ b/plumbing/format/index/encoder.go
@@ -14,7 +14,7 @@ import (
var (
// EncodeVersionSupported is the range of supported index versions
- EncodeVersionSupported uint32 = 2
+ EncodeVersionSupported uint32 = 3
// ErrInvalidTimestamp is returned by Encode if a Index with a Entry with
// negative timestamp values
@@ -36,9 +36,9 @@ func NewEncoder(w io.Writer) *Encoder {
// Encode writes the Index to the stream of the encoder.
func (e *Encoder) Encode(idx *Index) error {
- // TODO: support versions v3 and v4
+ // TODO: support v4
// TODO: support extensions
- if idx.Version != EncodeVersionSupported {
+ if idx.Version > EncodeVersionSupported {
return ErrUnsupportedVersion
}
@@ -68,8 +68,12 @@ func (e *Encoder) encodeEntries(idx *Index) error {
if err := e.encodeEntry(entry); err != nil {
return err
}
+ entryLength := entryHeaderLength
+ if entry.IntentToAdd || entry.SkipWorktree {
+ entryLength += 2
+ }
- wrote := entryHeaderLength + len(entry.Name)
+ wrote := entryLength + len(entry.Name)
if err := e.padEntry(wrote); err != nil {
return err
}
@@ -79,10 +83,6 @@ func (e *Encoder) encodeEntries(idx *Index) error {
}
func (e *Encoder) encodeEntry(entry *Entry) error {
- if entry.IntentToAdd || entry.SkipWorktree {
- return ErrUnsupportedVersion
- }
-
sec, nsec, err := e.timeToUint32(&entry.CreatedAt)
if err != nil {
return err
@@ -110,9 +110,25 @@ func (e *Encoder) encodeEntry(entry *Entry) error {
entry.GID,
entry.Size,
entry.Hash[:],
- flags,
}
+ flagsFlow := []interface{}{flags}
+
+ if entry.IntentToAdd || entry.SkipWorktree {
+ var extendedFlags uint16
+
+ if entry.IntentToAdd {
+ extendedFlags |= intentToAddMask
+ }
+ if entry.SkipWorktree {
+ extendedFlags |= skipWorkTreeMask
+ }
+
+ flagsFlow = []interface{}{flags | entryExtended, extendedFlags}
+ }
+
+ flow = append(flow, flagsFlow...)
+
if err := binary.Write(e.w, flow...); err != nil {
return err
}
diff --git a/plumbing/format/index/encoder_test.go b/plumbing/format/index/encoder_test.go
index b7a73cb..25c24f1 100644
--- a/plumbing/format/index/encoder_test.go
+++ b/plumbing/format/index/encoder_test.go
@@ -57,7 +57,7 @@ func (s *IndexSuite) TestEncode(c *C) {
}
func (s *IndexSuite) TestEncodeUnsupportedVersion(c *C) {
- idx := &Index{Version: 3}
+ idx := &Index{Version: 4}
buf := bytes.NewBuffer(nil)
e := NewEncoder(buf)
@@ -67,24 +67,40 @@ func (s *IndexSuite) TestEncodeUnsupportedVersion(c *C) {
func (s *IndexSuite) TestEncodeWithIntentToAddUnsupportedVersion(c *C) {
idx := &Index{
- Version: 2,
+ Version: 3,
Entries: []*Entry{{IntentToAdd: true}},
}
buf := bytes.NewBuffer(nil)
e := NewEncoder(buf)
err := e.Encode(idx)
- c.Assert(err, Equals, ErrUnsupportedVersion)
+ c.Assert(err, IsNil)
+
+ output := &Index{}
+ d := NewDecoder(buf)
+ err = d.Decode(output)
+ c.Assert(err, IsNil)
+
+ c.Assert(cmp.Equal(idx, output), Equals, true)
+ c.Assert(output.Entries[0].IntentToAdd, Equals, true)
}
func (s *IndexSuite) TestEncodeWithSkipWorktreeUnsupportedVersion(c *C) {
idx := &Index{
- Version: 2,
+ Version: 3,
Entries: []*Entry{{SkipWorktree: true}},
}
buf := bytes.NewBuffer(nil)
e := NewEncoder(buf)
err := e.Encode(idx)
- c.Assert(err, Equals, ErrUnsupportedVersion)
+ c.Assert(err, IsNil)
+
+ output := &Index{}
+ d := NewDecoder(buf)
+ err = d.Decode(output)
+ c.Assert(err, IsNil)
+
+ c.Assert(cmp.Equal(idx, output), Equals, true)
+ c.Assert(output.Entries[0].SkipWorktree, Equals, true)
}
diff --git a/plumbing/format/index/index.go b/plumbing/format/index/index.go
index 649416a..f4c7647 100644
--- a/plumbing/format/index/index.go
+++ b/plumbing/format/index/index.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"path/filepath"
+ "strings"
"time"
"github.com/go-git/go-git/v5/plumbing"
@@ -211,3 +212,20 @@ type EndOfIndexEntry struct {
// their contents).
Hash plumbing.Hash
}
+
+// SkipUnless applies patterns in the form of A, A/B, A/B/C
+// to the index to prevent the files from being checked out
+func (i *Index) SkipUnless(patterns []string) {
+ for _, e := range i.Entries {
+ var include bool
+ for _, pattern := range patterns {
+ if strings.HasPrefix(e.Name, pattern) {
+ include = true
+ break
+ }
+ }
+ if !include {
+ e.SkipWorktree = true
+ }
+ }
+}
diff --git a/plumbing/format/packfile/fsobject.go b/plumbing/format/packfile/fsobject.go
index a395d17..238339d 100644
--- a/plumbing/format/packfile/fsobject.go
+++ b/plumbing/format/packfile/fsobject.go
@@ -13,7 +13,6 @@ import (
// FSObject is an object from the packfile on the filesystem.
type FSObject struct {
hash plumbing.Hash
- h *ObjectHeader
offset int64
size int64
typ plumbing.ObjectType
@@ -118,17 +117,3 @@ func (o *FSObject) Type() plumbing.ObjectType {
func (o *FSObject) Writer() (io.WriteCloser, error) {
return nil, nil
}
-
-type objectReader struct {
- io.ReadCloser
- f billy.File
-}
-
-func (r *objectReader) Close() error {
- if err := r.ReadCloser.Close(); err != nil {
- _ = r.f.Close()
- return err
- }
-
- return r.f.Close()
-}
diff --git a/plumbing/format/packfile/packfile_test.go b/plumbing/format/packfile/packfile_test.go
index 6af8817..2eb099d 100644
--- a/plumbing/format/packfile/packfile_test.go
+++ b/plumbing/format/packfile/packfile_test.go
@@ -8,7 +8,6 @@ import (
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/idxfile"
"github.com/go-git/go-git/v5/plumbing/format/packfile"
- "github.com/go-git/go-git/v5/plumbing/storer"
. "gopkg.in/check.v1"
)
@@ -236,22 +235,6 @@ var expectedHashes = []string{
"7e59600739c96546163833214c36459e324bad0a",
}
-func assertObjects(c *C, s storer.EncodedObjectStorer, expects []string) {
- i, err := s.IterEncodedObjects(plumbing.AnyObject)
- c.Assert(err, IsNil)
-
- var count int
- err = i.ForEach(func(plumbing.EncodedObject) error { count++; return nil })
- c.Assert(err, IsNil)
- c.Assert(count, Equals, len(expects))
-
- for _, exp := range expects {
- obt, err := s.EncodedObject(plumbing.AnyObject, plumbing.NewHash(exp))
- c.Assert(err, IsNil)
- c.Assert(obt.Hash().String(), Equals, exp)
- }
-}
-
func getIndexFromIdxFile(r io.Reader) idxfile.Index {
idx := idxfile.NewMemoryIndex()
if err := idxfile.NewDecoder(r).Decode(idx); err != nil {
diff --git a/plumbing/format/packfile/parser.go b/plumbing/format/packfile/parser.go
index 4c28a4a..9ec838e 100644
--- a/plumbing/format/packfile/parser.go
+++ b/plumbing/format/packfile/parser.go
@@ -46,7 +46,6 @@ type Parser struct {
oi []*objectInfo
oiByHash map[plumbing.Hash]*objectInfo
oiByOffset map[int64]*objectInfo
- hashOffset map[plumbing.Hash]int64
checksum plumbing.Hash
cache *cache.BufferLRU
diff --git a/plumbing/object/change_adaptor.go b/plumbing/object/change_adaptor.go
index f701188..b96ee84 100644
--- a/plumbing/object/change_adaptor.go
+++ b/plumbing/object/change_adaptor.go
@@ -16,11 +16,11 @@ func newChange(c merkletrie.Change) (*Change, error) {
var err error
if ret.From, err = newChangeEntry(c.From); err != nil {
- return nil, fmt.Errorf("From field: %s", err)
+ return nil, fmt.Errorf("from field: %s", err)
}
if ret.To, err = newChangeEntry(c.To); err != nil {
- return nil, fmt.Errorf("To field: %s", err)
+ return nil, fmt.Errorf("to field: %s", err)
}
return ret, nil
diff --git a/plumbing/object/patch.go b/plumbing/object/patch.go
index 56b62c1..06bc35b 100644
--- a/plumbing/object/patch.go
+++ b/plumbing/object/patch.go
@@ -96,10 +96,6 @@ func filePatchWithContext(ctx context.Context, c *Change) (fdiff.FilePatch, erro
}
-func filePatch(c *Change) (fdiff.FilePatch, error) {
- return filePatchWithContext(context.Background(), c)
-}
-
func fileContent(f *File) (content string, isBinary bool, err error) {
if f == nil {
return
diff --git a/plumbing/object/treenoder.go b/plumbing/object/treenoder.go
index b4891b9..6e7b334 100644
--- a/plumbing/object/treenoder.go
+++ b/plumbing/object/treenoder.go
@@ -38,6 +38,10 @@ func NewTreeRootNode(t *Tree) noder.Noder {
}
}
+func (t *treeNoder) Skip() bool {
+ return false
+}
+
func (t *treeNoder) isRoot() bool {
return t.name == ""
}
diff --git a/plumbing/protocol/packp/common.go b/plumbing/protocol/packp/common.go
index ab07ac8..fef50a4 100644
--- a/plumbing/protocol/packp/common.go
+++ b/plumbing/protocol/packp/common.go
@@ -19,7 +19,6 @@ var (
// common
sp = []byte(" ")
eol = []byte("\n")
- eq = []byte{'='}
// advertised-refs
null = []byte("\x00")
diff --git a/plumbing/protocol/packp/updreq.go b/plumbing/protocol/packp/updreq.go
index 4d927d8..5dbd8ac 100644
--- a/plumbing/protocol/packp/updreq.go
+++ b/plumbing/protocol/packp/updreq.go
@@ -19,6 +19,7 @@ var (
type ReferenceUpdateRequest struct {
Capabilities *capability.List
Commands []*Command
+ Options []*Option
Shallow *plumbing.Hash
// Packfile contains an optional packfile reader.
Packfile io.ReadCloser
@@ -86,9 +87,9 @@ type Action string
const (
Create Action = "create"
- Update = "update"
- Delete = "delete"
- Invalid = "invalid"
+ Update Action = "update"
+ Delete Action = "delete"
+ Invalid Action = "invalid"
)
type Command struct {
@@ -120,3 +121,8 @@ func (c *Command) validate() error {
return nil
}
+
+type Option struct {
+ Key string
+ Value string
+}
diff --git a/plumbing/protocol/packp/updreq_encode.go b/plumbing/protocol/packp/updreq_encode.go
index 2545e93..1205cfa 100644
--- a/plumbing/protocol/packp/updreq_encode.go
+++ b/plumbing/protocol/packp/updreq_encode.go
@@ -9,10 +9,6 @@ import (
"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
)
-var (
- zeroHashString = plumbing.ZeroHash.String()
-)
-
// Encode writes the ReferenceUpdateRequest encoding to the stream.
func (req *ReferenceUpdateRequest) Encode(w io.Writer) error {
if err := req.validate(); err != nil {
@@ -29,6 +25,12 @@ func (req *ReferenceUpdateRequest) Encode(w io.Writer) error {
return err
}
+ if req.Capabilities.Supports(capability.PushOptions) {
+ if err := req.encodeOptions(e, req.Options); err != nil {
+ return err
+ }
+ }
+
if req.Packfile != nil {
if _, err := io.Copy(w, req.Packfile); err != nil {
return err
@@ -73,3 +75,15 @@ func formatCommand(cmd *Command) string {
n := cmd.New.String()
return fmt.Sprintf("%s %s %s", o, n, cmd.Name)
}
+
+func (req *ReferenceUpdateRequest) encodeOptions(e *pktline.Encoder,
+ opts []*Option) error {
+
+ for _, opt := range opts {
+ if err := e.Encodef("%s=%s", opt.Key, opt.Value); err != nil {
+ return err
+ }
+ }
+
+ return e.Flush()
+}
diff --git a/plumbing/protocol/packp/updreq_encode_test.go b/plumbing/protocol/packp/updreq_encode_test.go
index 5ad2b1b..4370b79 100644
--- a/plumbing/protocol/packp/updreq_encode_test.go
+++ b/plumbing/protocol/packp/updreq_encode_test.go
@@ -5,9 +5,11 @@ import (
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/pktline"
+ "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
- . "gopkg.in/check.v1"
"io/ioutil"
+
+ . "gopkg.in/check.v1"
)
type UpdReqEncodeSuite struct{}
@@ -142,3 +144,48 @@ func (s *UpdReqEncodeSuite) TestWithPackfile(c *C) {
s.testEncode(c, r, expected)
}
+
+func (s *UpdReqEncodeSuite) TestPushOptions(c *C) {
+ hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+ hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+ name := plumbing.ReferenceName("myref")
+
+ r := NewReferenceUpdateRequest()
+ r.Capabilities.Set(capability.PushOptions)
+ r.Commands = []*Command{
+ {Name: name, Old: hash1, New: hash2},
+ }
+ r.Options = []*Option{
+ {Key: "SomeKey", Value: "SomeValue"},
+ {Key: "AnotherKey", Value: "AnotherValue"},
+ }
+
+ expected := pktlines(c,
+ "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00push-options",
+ pktline.FlushString,
+ "SomeKey=SomeValue",
+ "AnotherKey=AnotherValue",
+ pktline.FlushString,
+ )
+
+ s.testEncode(c, r, expected)
+}
+
+func (s *UpdReqEncodeSuite) TestPushAtomic(c *C) {
+ hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+ hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+ name := plumbing.ReferenceName("myref")
+
+ r := NewReferenceUpdateRequest()
+ r.Capabilities.Set(capability.Atomic)
+ r.Commands = []*Command{
+ {Name: name, Old: hash1, New: hash2},
+ }
+
+ expected := pktlines(c,
+ "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00atomic",
+ pktline.FlushString,
+ )
+
+ s.testEncode(c, r, expected)
+}
diff --git a/plumbing/protocol/packp/uppackresp.go b/plumbing/protocol/packp/uppackresp.go
index a9a7192..26ae61e 100644
--- a/plumbing/protocol/packp/uppackresp.go
+++ b/plumbing/protocol/packp/uppackresp.go
@@ -24,7 +24,6 @@ type UploadPackResponse struct {
r io.ReadCloser
isShallow bool
isMultiACK bool
- isOk bool
}
// NewUploadPackResponse create a new UploadPackResponse instance, the request
diff --git a/plumbing/revlist/revlist_test.go b/plumbing/revlist/revlist_test.go
index a1ee504..9f2f93b 100644
--- a/plumbing/revlist/revlist_test.go
+++ b/plumbing/revlist/revlist_test.go
@@ -55,12 +55,6 @@ func (s *RevListSuite) SetUpTest(c *C) {
s.Storer = sto
}
-func (s *RevListSuite) commit(c *C, h plumbing.Hash) *object.Commit {
- commit, err := object.GetCommit(s.Storer, h)
- c.Assert(err, IsNil)
- return commit
-}
-
func (s *RevListSuite) TestRevListObjects_Submodules(c *C) {
submodules := map[string]bool{
"6ecf0ef2c2dffb796033e5a02219af86ec6584e5": true,
diff --git a/plumbing/transport/client/client_test.go b/plumbing/transport/client/client_test.go
index 9ebe113..92db525 100644
--- a/plumbing/transport/client/client_test.go
+++ b/plumbing/transport/client/client_test.go
@@ -1,7 +1,6 @@
package client
import (
- "fmt"
"net/http"
"testing"
@@ -68,7 +67,3 @@ func (*dummyClient) NewReceivePackSession(*transport.Endpoint, transport.AuthMet
transport.ReceivePackSession, error) {
return nil, nil
}
-
-func typeAsString(v interface{}) string {
- return fmt.Sprintf("%T", v)
-}
diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go
index fdb148f..d0e9a29 100644
--- a/plumbing/transport/internal/common/common.go
+++ b/plumbing/transport/internal/common/common.go
@@ -428,11 +428,6 @@ func isRepoNotFoundError(s string) bool {
return false
}
-var (
- nak = []byte("NAK")
- eol = []byte("\n")
-)
-
// uploadPack implements the git-upload-pack protocol.
func uploadPack(w io.WriteCloser, r io.Reader, req *packp.UploadPackRequest) error {
// TODO support multi_ack mode
diff --git a/plumbing/transport/ssh/common_test.go b/plumbing/transport/ssh/common_test.go
index e04a9c5..6d634d5 100644
--- a/plumbing/transport/ssh/common_test.go
+++ b/plumbing/transport/ssh/common_test.go
@@ -7,7 +7,6 @@ import (
"github.com/kevinburke/ssh_config"
"golang.org/x/crypto/ssh"
- stdssh "golang.org/x/crypto/ssh"
. "gopkg.in/check.v1"
)
@@ -99,7 +98,7 @@ func (s *SuiteCommon) TestIssue70(c *C) {
uploadPack.SetUpSuite(c)
config := &ssh.ClientConfig{
- HostKeyCallback: stdssh.InsecureIgnoreHostKey(),
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
r := &runner{
config: config,
diff --git a/prune.go b/prune.go
index cc5907a..8e35b99 100644
--- a/prune.go
+++ b/prune.go
@@ -17,7 +17,7 @@ type PruneOptions struct {
Handler PruneHandler
}
-var ErrLooseObjectsNotSupported = errors.New("Loose objects not supported")
+var ErrLooseObjectsNotSupported = errors.New("loose objects not supported")
// DeleteObject deletes an object from a repository.
// The type conveniently matches PruneHandler.
diff --git a/remote.go b/remote.go
index 4a06106..db78ae7 100644
--- a/remote.go
+++ b/remote.go
@@ -5,10 +5,12 @@ import (
"errors"
"fmt"
"io"
+ "strings"
"time"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5/config"
+ "github.com/go-git/go-git/v5/internal/url"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/format/packfile"
@@ -103,7 +105,11 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) {
return fmt.Errorf("remote names don't match: %s != %s", o.RemoteName, r.c.Name)
}
- s, err := newSendPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle)
+ if o.RemoteURL == "" {
+ o.RemoteURL = r.c.URLs[0]
+ }
+
+ s, err := newSendPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle)
if err != nil {
return err
}
@@ -183,12 +189,12 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) {
var hashesToPush []plumbing.Hash
// Avoid the expensive revlist operation if we're only doing deletes.
if !allDelete {
- if r.c.IsFirstURLLocal() {
+ if url.IsLocalEndpoint(o.RemoteURL) {
// If we're are pushing to a local repo, it might be much
// faster to use a local storage layer to get the commits
// to ignore, when calculating the object revlist.
localStorer := filesystem.NewStorage(
- osfs.New(r.c.URLs[0]), cache.NewObjectLRUDefault())
+ osfs.New(o.RemoteURL), cache.NewObjectLRUDefault())
hashesToPush, err = revlist.ObjectsWithStorageForIgnores(
r.s, localStorer, objects, haves)
} else {
@@ -225,6 +231,74 @@ func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool {
return !ar.Capabilities.Supports(capability.OFSDelta)
}
+func (r *Remote) addReachableTags(localRefs []*plumbing.Reference, remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error {
+ tags := make(map[plumbing.Reference]struct{})
+ // get a list of all tags locally
+ for _, ref := range localRefs {
+ if strings.HasPrefix(string(ref.Name()), "refs/tags") {
+ tags[*ref] = struct{}{}
+ }
+ }
+
+ remoteRefIter, err := remoteRefs.IterReferences()
+ if err != nil {
+ return err
+ }
+
+ // remove any that are already on the remote
+ if err := remoteRefIter.ForEach(func(reference *plumbing.Reference) error {
+ delete(tags, *reference)
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ for tag := range tags {
+ tagObject, err := object.GetObject(r.s, tag.Hash())
+ var tagCommit *object.Commit
+ if err != nil {
+ return fmt.Errorf("get tag object: %w", err)
+ }
+
+ if tagObject.Type() != plumbing.TagObject {
+ continue
+ }
+
+ annotatedTag, ok := tagObject.(*object.Tag)
+ if !ok {
+ return errors.New("could not get annotated tag object")
+ }
+
+ tagCommit, err = object.GetCommit(r.s, annotatedTag.Target)
+ if err != nil {
+ return fmt.Errorf("get annotated tag commit: %w", err)
+ }
+
+ // only include tags that are reachable from one of the refs
+ // already being pushed
+ for _, cmd := range req.Commands {
+ if tag.Name() == cmd.Name {
+ continue
+ }
+
+ if strings.HasPrefix(cmd.Name.String(), "refs/tags") {
+ continue
+ }
+
+ c, err := object.GetCommit(r.s, cmd.New)
+ if err != nil {
+ return fmt.Errorf("get commit %v: %w", cmd.Name, err)
+ }
+
+ if isAncestor, err := tagCommit.IsAncestor(c); err == nil && isAncestor {
+ req.Commands = append(req.Commands, &packp.Command{Name: tag.Name(), New: tag.Hash()})
+ }
+ }
+ }
+
+ return nil
+}
+
func (r *Remote) newReferenceUpdateRequest(
o *PushOptions,
localRefs []*plumbing.Reference,
@@ -242,10 +316,28 @@ func (r *Remote) newReferenceUpdateRequest(
}
}
- if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune); err != nil {
+ if ar.Capabilities.Supports(capability.PushOptions) {
+ _ = req.Capabilities.Set(capability.PushOptions)
+ for k, v := range o.Options {
+ req.Options = append(req.Options, &packp.Option{Key: k, Value: v})
+ }
+ }
+
+ if o.Atomic && ar.Capabilities.Supports(capability.Atomic) {
+ _ = req.Capabilities.Set(capability.Atomic)
+ }
+
+ if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune, o.ForceWithLease); err != nil {
+
return nil, err
}
+ if o.FollowTags {
+ if err := r.addReachableTags(localRefs, remoteRefs, req); err != nil {
+ return nil, err
+ }
+ }
+
return req, nil
}
@@ -314,7 +406,11 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen
o.RefSpecs = r.c.Fetch
}
- s, err := newUploadPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle)
+ if o.RemoteURL == "" {
+ o.RemoteURL = r.c.URLs[0]
+ }
+
+ s, err := newUploadPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle)
if err != nil {
return nil, err
}
@@ -474,6 +570,7 @@ func (r *Remote) addReferencesToUpdate(
remoteRefs storer.ReferenceStorer,
req *packp.ReferenceUpdateRequest,
prune bool,
+ forceWithLease *ForceWithLease,
) error {
// This references dictionary will be used to search references by name.
refsDict := make(map[string]*plumbing.Reference)
@@ -487,7 +584,7 @@ func (r *Remote) addReferencesToUpdate(
return err
}
} else {
- err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req)
+ err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req, forceWithLease)
if err != nil {
return err
}
@@ -509,20 +606,25 @@ func (r *Remote) addOrUpdateReferences(
refsDict map[string]*plumbing.Reference,
remoteRefs storer.ReferenceStorer,
req *packp.ReferenceUpdateRequest,
+ forceWithLease *ForceWithLease,
) error {
// If it is not a wilcard refspec we can directly search for the reference
// in the references dictionary.
if !rs.IsWildcard() {
ref, ok := refsDict[rs.Src()]
if !ok {
+ commit, err := object.GetCommit(r.s, plumbing.NewHash(rs.Src()))
+ if err == nil {
+ return r.addCommit(rs, remoteRefs, commit.Hash, req)
+ }
return nil
}
- return r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req)
+ return r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease)
}
for _, ref := range localRefs {
- err := r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req)
+ err := r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease)
if err != nil {
return err
}
@@ -569,9 +671,46 @@ func (r *Remote) deleteReferences(rs config.RefSpec,
})
}
+func (r *Remote) addCommit(rs config.RefSpec,
+ remoteRefs storer.ReferenceStorer, localCommit plumbing.Hash,
+ req *packp.ReferenceUpdateRequest) error {
+
+ if rs.IsWildcard() {
+ return errors.New("can't use wildcard together with hash refspecs")
+ }
+
+ cmd := &packp.Command{
+ Name: rs.Dst(""),
+ Old: plumbing.ZeroHash,
+ New: localCommit,
+ }
+ remoteRef, err := remoteRefs.Reference(cmd.Name)
+ if err == nil {
+ if remoteRef.Type() != plumbing.HashReference {
+ //TODO: check actual git behavior here
+ return nil
+ }
+
+ cmd.Old = remoteRef.Hash()
+ } else if err != plumbing.ErrReferenceNotFound {
+ return err
+ }
+ if cmd.Old == cmd.New {
+ return nil
+ }
+ if !rs.IsForceUpdate() {
+ if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil {
+ return err
+ }
+ }
+
+ req.Commands = append(req.Commands, cmd)
+ return nil
+}
+
func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference,
- req *packp.ReferenceUpdateRequest) error {
+ req *packp.ReferenceUpdateRequest, forceWithLease *ForceWithLease) error {
if localRef.Type() != plumbing.HashReference {
return nil
@@ -603,7 +742,11 @@ func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
return nil
}
- if !rs.IsForceUpdate() {
+ if forceWithLease != nil {
+ if err = r.checkForceWithLease(localRef, cmd, forceWithLease); err != nil {
+ return err
+ }
+ } else if !rs.IsForceUpdate() {
if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil {
return err
}
@@ -613,6 +756,31 @@ func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
return nil
}
+func (r *Remote) checkForceWithLease(localRef *plumbing.Reference, cmd *packp.Command, forceWithLease *ForceWithLease) error {
+ remotePrefix := fmt.Sprintf("refs/remotes/%s/", r.Config().Name)
+
+ ref, err := storer.ResolveReference(
+ r.s,
+ plumbing.ReferenceName(remotePrefix+strings.Replace(localRef.Name().String(), "refs/heads/", "", -1)))
+ if err != nil {
+ return err
+ }
+
+ if forceWithLease.RefName.String() == "" || (forceWithLease.RefName == cmd.Name) {
+ expectedOID := ref.Hash()
+
+ if !forceWithLease.Hash.IsZero() {
+ expectedOID = forceWithLease.Hash
+ }
+
+ if cmd.Old != expectedOID {
+ return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String())
+ }
+ }
+
+ return nil
+}
+
func (r *Remote) references() ([]*plumbing.Reference, error) {
var localRefs []*plumbing.Reference
diff --git a/remote_test.go b/remote_test.go
index 1efc9da..d0c8fa8 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -5,12 +5,16 @@ import (
"context"
"errors"
"io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"runtime"
"time"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
+ "github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
"github.com/go-git/go-git/v5/plumbing/storer"
@@ -46,6 +50,12 @@ func (s *RemoteSuite) TestFetchInvalidSchemaEndpoint(c *C) {
c.Assert(err, ErrorMatches, ".*unsupported scheme.*")
}
+func (s *RemoteSuite) TestFetchOverriddenEndpoint(c *C) {
+ r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://perfectly-valid-url.example.com"}})
+ err := r.Fetch(&FetchOptions{RemoteURL: "http://\\"})
+ c.Assert(err, ErrorMatches, ".*invalid character.*")
+}
+
func (s *RemoteSuite) TestFetchInvalidFetchOptions(c *C) {
r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}})
invalid := config.RefSpec("^*$ñ")
@@ -591,6 +601,66 @@ func (s *RemoteSuite) TestPushTags(c *C) {
})
}
+func (s *RemoteSuite) TestPushFollowTags(c *C) {
+ url, clean := s.TemporalDir()
+ defer clean()
+
+ server, err := PlainInit(url, true)
+ c.Assert(err, IsNil)
+
+ fs := fixtures.ByURL("https://github.com/git-fixtures/basic.git").One().DotGit()
+ sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
+
+ r := NewRemote(sto, &config.RemoteConfig{
+ Name: DefaultRemoteName,
+ URLs: []string{url},
+ })
+
+ localRepo := newRepository(sto, fs)
+ tipTag, err := localRepo.CreateTag(
+ "tip",
+ plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"),
+ &CreateTagOptions{
+ Message: "an annotated tag",
+ },
+ )
+ c.Assert(err, IsNil)
+
+ initialTag, err := localRepo.CreateTag(
+ "initial-commit",
+ plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
+ &CreateTagOptions{
+ Message: "a tag for the initial commit",
+ },
+ )
+ c.Assert(err, IsNil)
+
+ _, err = localRepo.CreateTag(
+ "master-tag",
+ plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
+ &CreateTagOptions{
+ Message: "a tag with a commit not reachable from branch",
+ },
+ )
+ c.Assert(err, IsNil)
+
+ err = r.Push(&PushOptions{
+ RefSpecs: []config.RefSpec{"+refs/heads/branch:refs/heads/branch"},
+ FollowTags: true,
+ })
+ c.Assert(err, IsNil)
+
+ AssertReferences(c, server, map[string]string{
+ "refs/heads/branch": "e8d3ffab552895c19b9fcf7aa264d277cde33881",
+ "refs/tags/tip": tipTag.Hash().String(),
+ "refs/tags/initial-commit": initialTag.Hash().String(),
+ })
+
+ AssertReferencesMissing(c, server, []string{
+ "refs/tags/master-tag",
+ })
+}
+
func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate(c *C) {
fs := fixtures.Basic().One().DotGit()
sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
@@ -746,6 +816,133 @@ func (s *RemoteSuite) TestPushForceWithOption(c *C) {
c.Assert(newRef, Not(DeepEquals), oldRef)
}
+func (s *RemoteSuite) TestPushForceWithLease_success(c *C) {
+ testCases := []struct {
+ desc string
+ forceWithLease ForceWithLease
+ }{
+ {
+ desc: "no arguments",
+ forceWithLease: ForceWithLease{},
+ },
+ {
+ desc: "ref name",
+ forceWithLease: ForceWithLease{
+ RefName: plumbing.ReferenceName("refs/heads/branch"),
+ },
+ },
+ {
+ desc: "ref name and sha",
+ forceWithLease: ForceWithLease{
+ RefName: plumbing.ReferenceName("refs/heads/branch"),
+ Hash: plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"),
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ c.Log("Executing test cases:", tc.desc)
+
+ f := fixtures.Basic().One()
+ sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
+ dstFs := f.DotGit()
+ dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault())
+
+ newCommit := plumbing.NewHashReference(
+ "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
+ )
+ c.Assert(sto.SetReference(newCommit), IsNil)
+
+ ref, err := sto.Reference("refs/heads/branch")
+ c.Log(ref.String())
+
+ url := dstFs.Root()
+ r := NewRemote(sto, &config.RemoteConfig{
+ Name: DefaultRemoteName,
+ URLs: []string{url},
+ })
+
+ oldRef, err := dstSto.Reference("refs/heads/branch")
+ c.Assert(err, IsNil)
+ c.Assert(oldRef, NotNil)
+
+ c.Assert(r.Push(&PushOptions{
+ RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"},
+ ForceWithLease: &ForceWithLease{},
+ }), IsNil)
+
+ newRef, err := dstSto.Reference("refs/heads/branch")
+ c.Assert(err, IsNil)
+ c.Assert(newRef, DeepEquals, newCommit)
+ }
+}
+
+func (s *RemoteSuite) TestPushForceWithLease_failure(c *C) {
+ testCases := []struct {
+ desc string
+ forceWithLease ForceWithLease
+ }{
+ {
+ desc: "no arguments",
+ forceWithLease: ForceWithLease{},
+ },
+ {
+ desc: "ref name",
+ forceWithLease: ForceWithLease{
+ RefName: plumbing.ReferenceName("refs/heads/branch"),
+ },
+ },
+ {
+ desc: "ref name and sha",
+ forceWithLease: ForceWithLease{
+ RefName: plumbing.ReferenceName("refs/heads/branch"),
+ Hash: plumbing.NewHash("152175bf7e5580299fa1f0ba41ef6474cc043b70"),
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ c.Log("Executing test cases:", tc.desc)
+
+ f := fixtures.Basic().One()
+ sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
+ c.Assert(sto.SetReference(
+ plumbing.NewHashReference(
+ "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
+ ),
+ ), IsNil)
+
+ dstFs := f.DotGit()
+ dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault())
+ c.Assert(dstSto.SetReference(
+ plumbing.NewHashReference(
+ "refs/heads/branch", plumbing.NewHash("ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
+ ),
+ ), IsNil)
+
+ url := dstFs.Root()
+ r := NewRemote(sto, &config.RemoteConfig{
+ Name: DefaultRemoteName,
+ URLs: []string{url},
+ })
+
+ oldRef, err := dstSto.Reference("refs/heads/branch")
+ c.Assert(err, IsNil)
+ c.Assert(oldRef, NotNil)
+
+ err = r.Push(&PushOptions{
+ RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"},
+ ForceWithLease: &ForceWithLease{},
+ })
+
+ c.Assert(err, DeepEquals, errors.New("non-fast-forward update: refs/heads/branch"))
+
+ newRef, err := dstSto.Reference("refs/heads/branch")
+ c.Assert(err, IsNil)
+ c.Assert(newRef, Not(DeepEquals), plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"))
+ }
+}
+
func (s *RemoteSuite) TestPushPrune(c *C) {
fs := fixtures.Basic().One().DotGit()
@@ -805,7 +1002,7 @@ func (s *RemoteSuite) TestPushPrune(c *C) {
"refs/remotes/origin/master": ref.Hash().String(),
})
- ref, err = server.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true)
+ _, err = server.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true)
c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
}
@@ -903,6 +1100,12 @@ func (s *RemoteSuite) TestPushNonExistentEndpoint(c *C) {
c.Assert(err, NotNil)
}
+func (s *RemoteSuite) TestPushOverriddenEndpoint(c *C) {
+ r := NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{"http://perfectly-valid-url.example.com"}})
+ err := r.Push(&PushOptions{RemoteURL: "http://\\"})
+ c.Assert(err, ErrorMatches, ".*invalid character.*")
+}
+
func (s *RemoteSuite) TestPushInvalidSchemaEndpoint(c *C) {
r := NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{"qux://foo"}})
err := r.Push(&PushOptions{})
@@ -1134,3 +1337,91 @@ func (s *RemoteSuite) TestPushRequireRemoteRefs(c *C) {
c.Assert(err, IsNil)
c.Assert(newRef, Not(DeepEquals), oldRef)
}
+
+func (s *RemoteSuite) TestCanPushShasToReference(c *C) {
+ d, err := ioutil.TempDir("", "TestCanPushShasToReference")
+ c.Assert(err, IsNil)
+ if err != nil {
+ return
+ }
+ defer os.RemoveAll(d)
+
+ // remote currently forces a plain path for path based remotes inside the PushContext function.
+ // This makes it impossible, in the current state to use memfs.
+ // For the sake of readability, use the same osFS everywhere and use plain git repositories on temporary files
+ remote, err := PlainInit(filepath.Join(d, "remote"), true)
+ c.Assert(err, IsNil)
+ c.Assert(remote, NotNil)
+
+ repo, err := PlainInit(filepath.Join(d, "repo"), false)
+ c.Assert(err, IsNil)
+ c.Assert(repo, NotNil)
+
+ fd, err := os.Create(filepath.Join(d, "repo", "README.md"))
+ c.Assert(err, IsNil)
+ if err != nil {
+ return
+ }
+ _, err = fd.WriteString("# test repo")
+ c.Assert(err, IsNil)
+ if err != nil {
+ return
+ }
+ err = fd.Close()
+ c.Assert(err, IsNil)
+ if err != nil {
+ return
+ }
+
+ wt, err := repo.Worktree()
+ c.Assert(err, IsNil)
+ if err != nil {
+ return
+ }
+
+ wt.Add("README.md")
+ sha, err := wt.Commit("test commit", &CommitOptions{
+ Author: &object.Signature{
+ Name: "test",
+ Email: "test@example.com",
+ When: time.Now(),
+ },
+ Committer: &object.Signature{
+ Name: "test",
+ Email: "test@example.com",
+ When: time.Now(),
+ },
+ })
+ c.Assert(err, IsNil)
+ if err != nil {
+ return
+ }
+
+ gitremote, err := repo.CreateRemote(&config.RemoteConfig{
+ Name: "local",
+ URLs: []string{filepath.Join(d, "remote")},
+ })
+ c.Assert(err, IsNil)
+ if err != nil {
+ return
+ }
+
+ err = gitremote.Push(&PushOptions{
+ RemoteName: "local",
+ RefSpecs: []config.RefSpec{
+ // TODO: check with short hashes that this is still respected
+ config.RefSpec(sha.String() + ":refs/heads/branch"),
+ },
+ })
+ c.Assert(err, IsNil)
+ if err != nil {
+ return
+ }
+
+ ref, err := remote.Reference(plumbing.ReferenceName("refs/heads/branch"), false)
+ c.Assert(err, IsNil)
+ if err != nil {
+ return
+ }
+ c.Assert(ref.Hash().String(), Equals, sha.String())
+}
diff --git a/repository.go b/repository.go
index d3fbf97..e8eb53f 100644
--- a/repository.go
+++ b/repository.go
@@ -56,7 +56,7 @@ var (
ErrWorktreeNotProvided = errors.New("worktree should be provided")
ErrIsBareRepository = errors.New("worktree not available in a bare repository")
ErrUnableToResolveCommit = errors.New("unable to resolve commit")
- ErrPackedObjectsNotSupported = errors.New("Packed objects not supported")
+ ErrPackedObjectsNotSupported = errors.New("packed objects not supported")
)
// Repository represents a git repository
@@ -1547,7 +1547,7 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
}
if c == nil {
- return &plumbing.ZeroHash, fmt.Errorf(`No commit message match regexp : "%s"`, re.String())
+ return &plumbing.ZeroHash, fmt.Errorf("no commit message match regexp: %q", re.String())
}
commit = c
diff --git a/repository_test.go b/repository_test.go
index 2bc5c90..e284df8 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -210,6 +210,37 @@ func (s *RepositorySuite) TestCloneWithTags(c *C) {
c.Assert(count, Equals, 3)
}
+func (s *RepositorySuite) TestCloneSparse(c *C) {
+ fs := memfs.New()
+ r, err := Clone(memory.NewStorage(), fs, &CloneOptions{
+ URL: s.GetBasicLocalRepositoryURL(),
+ })
+ c.Assert(err, IsNil)
+
+ w, err := r.Worktree()
+ c.Assert(err, IsNil)
+
+ sparseCheckoutDirectories := []string{"go", "json", "php"}
+ c.Assert(w.Checkout(&CheckoutOptions{
+ Branch: "refs/heads/master",
+ SparseCheckoutDirectories: sparseCheckoutDirectories,
+ }), IsNil)
+
+ fis, err := fs.ReadDir(".")
+ c.Assert(err, IsNil)
+ for _, fi := range fis {
+ c.Assert(fi.IsDir(), Equals, true)
+ var oneOfSparseCheckoutDirs bool
+
+ for _, sparseCheckoutDirectory := range sparseCheckoutDirectories {
+ if strings.HasPrefix(fi.Name(), sparseCheckoutDirectory) {
+ oneOfSparseCheckoutDirs = true
+ }
+ }
+ c.Assert(oneOfSparseCheckoutDirs, Equals, true)
+ }
+}
+
func (s *RepositorySuite) TestCreateRemoteAndRemote(c *C) {
r, _ := Init(memory.NewStorage(), nil)
remote, err := r.CreateRemote(&config.RemoteConfig{
@@ -2756,7 +2787,7 @@ func (s *RepositorySuite) TestResolveRevisionWithErrors(c *C) {
datas := map[string]string{
"efs/heads/master~": "reference not found",
"HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`,
- "HEAD^{/whatever}": `No commit message match regexp : "whatever"`,
+ "HEAD^{/whatever}": `no commit message match regexp: "whatever"`,
"4e1243bd22c66e76c2ba9eddc1f91394e57f9f83": "reference not found",
}
diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go
index 4c2ae94..a8f0eb7 100644
--- a/storage/filesystem/dotgit/dotgit_test.go
+++ b/storage/filesystem/dotgit/dotgit_test.go
@@ -3,6 +3,7 @@ package dotgit
import (
"bufio"
"encoding/hex"
+ "io"
"io/ioutil"
"os"
"path/filepath"
@@ -510,13 +511,13 @@ func (s *SuiteDotGit) TestObjectPackWithKeepDescriptors(c *C) {
c.Assert(filepath.Ext(pack.Name()), Equals, ".pack")
// Move to an specific offset
- pack.Seek(42, os.SEEK_SET)
+ pack.Seek(42, io.SeekStart)
pack2, err := dir.ObjectPack(plumbing.NewHash(f.PackfileHash))
c.Assert(err, IsNil)
// If the file is the same the offset should be the same
- offset, err := pack2.Seek(0, os.SEEK_CUR)
+ offset, err := pack2.Seek(0, io.SeekCurrent)
c.Assert(err, IsNil)
c.Assert(offset, Equals, int64(42))
@@ -527,7 +528,7 @@ func (s *SuiteDotGit) TestObjectPackWithKeepDescriptors(c *C) {
c.Assert(err, IsNil)
// If the file is opened again its offset should be 0
- offset, err = pack2.Seek(0, os.SEEK_CUR)
+ offset, err = pack2.Seek(0, io.SeekCurrent)
c.Assert(err, IsNil)
c.Assert(offset, Equals, int64(0))
@@ -653,7 +654,7 @@ func (s *SuiteDotGit) TestObject(c *C) {
fs.MkdirAll(incomingDirPath, os.FileMode(0755))
fs.Create(incomingFilePath)
- file, err = dir.Object(plumbing.NewHash(incomingHash))
+ _, err = dir.Object(plumbing.NewHash(incomingHash))
c.Assert(err, IsNil)
}
diff --git a/storage/filesystem/dotgit/reader.go b/storage/filesystem/dotgit/reader.go
index a82ac94..975f92a 100644
--- a/storage/filesystem/dotgit/reader.go
+++ b/storage/filesystem/dotgit/reader.go
@@ -66,7 +66,7 @@ func (e *EncodedObject) Size() int64 {
func (e *EncodedObject) SetSize(int64) {}
func (e *EncodedObject) Writer() (io.WriteCloser, error) {
- return nil, fmt.Errorf("Not supported")
+ return nil, fmt.Errorf("not supported")
}
func NewEncodedObject(dir *DotGit, h plumbing.Hash, t plumbing.ObjectType, size int64) *EncodedObject {
diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go
index 59b40d3..19a7914 100644
--- a/storage/filesystem/object_test.go
+++ b/storage/filesystem/object_test.go
@@ -71,7 +71,7 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) {
pack1, err := dg.ObjectPack(packfiles[0])
c.Assert(err, IsNil)
- pack1.Seek(42, os.SEEK_SET)
+ pack1.Seek(42, io.SeekStart)
err = o.Close()
c.Assert(err, IsNil)
@@ -79,7 +79,7 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) {
pack2, err := dg.ObjectPack(packfiles[0])
c.Assert(err, IsNil)
- offset, err := pack2.Seek(0, os.SEEK_CUR)
+ offset, err := pack2.Seek(0, io.SeekCurrent)
c.Assert(err, IsNil)
c.Assert(offset, Equals, int64(0))
@@ -386,7 +386,7 @@ func (s *FsSuite) TestGetFromObjectFileSharedCache(c *C) {
c.Assert(err, IsNil)
c.Assert(obj.Hash(), Equals, expected)
- obj, err = o2.EncodedObject(plumbing.CommitObject, expected)
+ _, err = o2.EncodedObject(plumbing.CommitObject, expected)
c.Assert(err, Equals, plumbing.ErrObjectNotFound)
}
diff --git a/storage/memory/storage.go b/storage/memory/storage.go
index a8e5669..ef6a445 100644
--- a/storage/memory/storage.go
+++ b/storage/memory/storage.go
@@ -193,7 +193,7 @@ func (o *ObjectStorage) DeleteOldObjectPackAndIndex(plumbing.Hash, time.Time) er
return nil
}
-var errNotSupported = fmt.Errorf("Not supported")
+var errNotSupported = fmt.Errorf("not supported")
func (o *ObjectStorage) LooseObjectTime(hash plumbing.Hash) (time.Time, error) {
return time.Time{}, errNotSupported
diff --git a/storage/transactional/config_test.go b/storage/transactional/config_test.go
index 1f3a572..34d7763 100644
--- a/storage/transactional/config_test.go
+++ b/storage/transactional/config_test.go
@@ -54,7 +54,7 @@ func (s *ConfigSuite) TestSetConfigTemporal(c *C) {
cfg, err = cs.Config()
c.Assert(err, IsNil)
- c.Assert(temporalCfg.Core.Worktree, Equals, "bar")
+ c.Assert(cfg.Core.Worktree, Equals, "bar")
}
func (s *ConfigSuite) TestCommit(c *C) {
diff --git a/storage/transactional/reference.go b/storage/transactional/reference.go
index 3b009e2..1c09307 100644
--- a/storage/transactional/reference.go
+++ b/storage/transactional/reference.go
@@ -15,9 +15,6 @@ type ReferenceStorage struct {
// commit is requested, the entries are added when RemoveReference is called
// and deleted if SetReference is called.
deleted map[plumbing.ReferenceName]struct{}
- // packRefs if true PackRefs is going to be called in the based storer when
- // commit is called.
- packRefs bool
}
// NewReferenceStorage returns a new ReferenceStorer based on a base storer and
@@ -108,7 +105,6 @@ func (r ReferenceStorage) CountLooseRefs() (int, error) {
// PackRefs honors the storer.ReferenceStorer interface.
func (r ReferenceStorage) PackRefs() error {
- r.packRefs = true
return nil
}
diff --git a/utils/merkletrie/difftree.go b/utils/merkletrie/difftree.go
index bd084b2..9f5145a 100644
--- a/utils/merkletrie/difftree.go
+++ b/utils/merkletrie/difftree.go
@@ -304,13 +304,38 @@ func DiffTreeContext(ctx context.Context, fromTree, toTree noder.Noder,
return nil, err
}
case onlyToRemains:
- if err = ret.AddRecursiveInsert(to); err != nil {
- return nil, err
+ if to.Skip() {
+ if err = ret.AddRecursiveDelete(to); err != nil {
+ return nil, err
+ }
+ } else {
+ if err = ret.AddRecursiveInsert(to); err != nil {
+ return nil, err
+ }
}
if err = ii.nextTo(); err != nil {
return nil, err
}
case bothHaveNodes:
+ if from.Skip() {
+ if err = ret.AddRecursiveDelete(from); err != nil {
+ return nil, err
+ }
+ if err := ii.nextBoth(); err != nil {
+ return nil, err
+ }
+ break
+ }
+ if to.Skip() {
+ if err = ret.AddRecursiveDelete(to); err != nil {
+ return nil, err
+ }
+ if err := ii.nextBoth(); err != nil {
+ return nil, err
+ }
+ break
+ }
+
if err = diffNodes(&ret, ii); err != nil {
return nil, err
}
diff --git a/utils/merkletrie/filesystem/node.go b/utils/merkletrie/filesystem/node.go
index 2fc3d7a..ad169ff 100644
--- a/utils/merkletrie/filesystem/node.go
+++ b/utils/merkletrie/filesystem/node.go
@@ -61,6 +61,10 @@ func (n *node) IsDir() bool {
return n.isDir
}
+func (n *node) Skip() bool {
+ return false
+}
+
func (n *node) Children() ([]noder.Noder, error) {
if err := n.calculateChildren(); err != nil {
return nil, err
diff --git a/utils/merkletrie/index/node.go b/utils/merkletrie/index/node.go
index d05b0c6..c1809f7 100644
--- a/utils/merkletrie/index/node.go
+++ b/utils/merkletrie/index/node.go
@@ -19,6 +19,7 @@ type node struct {
entry *index.Entry
children []noder.Noder
isDir bool
+ skip bool
}
// NewRootNode returns the root node of a computed tree from a index.Index,
@@ -39,7 +40,7 @@ func NewRootNode(idx *index.Index) noder.Noder {
continue
}
- n := &node{path: fullpath}
+ n := &node{path: fullpath, skip: e.SkipWorktree}
if fullpath == e.Name {
n.entry = e
} else {
@@ -58,6 +59,10 @@ func (n *node) String() string {
return n.path
}
+func (n *node) Skip() bool {
+ return n.skip
+}
+
// Hash the hash of a filesystem is a 24-byte slice, is the result of
// concatenating the computed plumbing.Hash of the file as a Blob and its
// plumbing.FileMode; that way the difftree algorithm will detect changes in the
diff --git a/utils/merkletrie/internal/fsnoder/dir.go b/utils/merkletrie/internal/fsnoder/dir.go
index 20a2aee..3a4c242 100644
--- a/utils/merkletrie/internal/fsnoder/dir.go
+++ b/utils/merkletrie/internal/fsnoder/dir.go
@@ -112,6 +112,10 @@ func (d *dir) NumChildren() (int, error) {
return len(d.children), nil
}
+func (d *dir) Skip() bool {
+ return false
+}
+
const (
dirStartMark = '('
dirEndMark = ')'
diff --git a/utils/merkletrie/internal/fsnoder/file.go b/utils/merkletrie/internal/fsnoder/file.go
index d53643f..0bb908b 100644
--- a/utils/merkletrie/internal/fsnoder/file.go
+++ b/utils/merkletrie/internal/fsnoder/file.go
@@ -55,6 +55,10 @@ func (f *file) NumChildren() (int, error) {
return 0, nil
}
+func (f *file) Skip() bool {
+ return false
+}
+
const (
fileStartMark = '<'
fileEndMark = '>'
diff --git a/utils/merkletrie/noder/noder.go b/utils/merkletrie/noder/noder.go
index d6b3de4..6d22b8c 100644
--- a/utils/merkletrie/noder/noder.go
+++ b/utils/merkletrie/noder/noder.go
@@ -53,6 +53,7 @@ type Noder interface {
// implement NumChildren in O(1) while Children is usually more
// complex.
NumChildren() (int, error)
+ Skip() bool
}
// NoChildren represents the children of a noder without children.
diff --git a/utils/merkletrie/noder/noder_test.go b/utils/merkletrie/noder/noder_test.go
index 5e014fe..c1af998 100644
--- a/utils/merkletrie/noder/noder_test.go
+++ b/utils/merkletrie/noder/noder_test.go
@@ -25,6 +25,7 @@ func (n noderMock) Name() string { return n.name }
func (n noderMock) IsDir() bool { return n.isDir }
func (n noderMock) Children() ([]Noder, error) { return n.children, nil }
func (n noderMock) NumChildren() (int, error) { return len(n.children), nil }
+func (n noderMock) Skip() bool { return false }
// Returns a sequence with the noders 3, 2, and 1 from the
// following diagram:
@@ -57,20 +58,6 @@ func childrenFixture() []Noder {
return []Noder{c1, c2}
}
-// Returns the same as nodersFixture but sorted by name, this is: "1",
-// "2" and then "3".
-func sortedNodersFixture() []Noder {
- n1 := &noderMock{
- name: "1",
- hash: []byte{0x00, 0x01, 0x02},
- isDir: true,
- children: childrenFixture(),
- }
- n2 := &noderMock{name: "2"}
- n3 := &noderMock{name: "3"}
- return []Noder{n1, n2, n3} // the same as nodersFixture but sorted by name
-}
-
// returns nodersFixture as the path of "1".
func pathFixture() Path {
return Path(nodersFixture())
diff --git a/utils/merkletrie/noder/path.go b/utils/merkletrie/noder/path.go
index 1c7ef54..6c1d363 100644
--- a/utils/merkletrie/noder/path.go
+++ b/utils/merkletrie/noder/path.go
@@ -15,6 +15,14 @@ import (
// not be used.
type Path []Noder
+func (p Path) Skip() bool {
+ if len(p) > 0 {
+ return p.Last().Skip()
+ }
+
+ return false
+}
+
// String returns the full path of the final noder as a string, using
// "/" as the separator.
func (p Path) String() string {
diff --git a/worktree.go b/worktree.go
index f23d9f1..c974aed 100644
--- a/worktree.go
+++ b/worktree.go
@@ -11,6 +11,8 @@ import (
"strings"
"sync"
+ "github.com/go-git/go-billy/v5"
+ "github.com/go-git/go-billy/v5/util"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode"
@@ -20,9 +22,6 @@ import (
"github.com/go-git/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/utils/ioutil"
"github.com/go-git/go-git/v5/utils/merkletrie"
-
- "github.com/go-git/go-billy/v5"
- "github.com/go-git/go-billy/v5/util"
)
var (
@@ -73,6 +72,7 @@ func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error {
fetchHead, err := remote.fetch(ctx, &FetchOptions{
RemoteName: o.RemoteName,
+ RemoteURL: o.RemoteURL,
Depth: o.Depth,
Auth: o.Auth,
Progress: o.Progress,
@@ -182,6 +182,10 @@ func (w *Worktree) Checkout(opts *CheckoutOptions) error {
return err
}
+ if len(opts.SparseCheckoutDirectories) > 0 {
+ return w.ResetSparsely(ro, opts.SparseCheckoutDirectories)
+ }
+
return w.Reset(ro)
}
func (w *Worktree) createBranch(opts *CheckoutOptions) error {
@@ -262,8 +266,7 @@ func (w *Worktree) setHEADToBranch(branch plumbing.ReferenceName, commit plumbin
return w.r.Storer.SetReference(head)
}
-// Reset the worktree to a specified state.
-func (w *Worktree) Reset(opts *ResetOptions) error {
+func (w *Worktree) ResetSparsely(opts *ResetOptions, dirs []string) error {
if err := opts.Validate(w.r); err != nil {
return err
}
@@ -293,7 +296,7 @@ func (w *Worktree) Reset(opts *ResetOptions) error {
}
if opts.Mode == MixedReset || opts.Mode == MergeReset || opts.Mode == HardReset {
- if err := w.resetIndex(t); err != nil {
+ if err := w.resetIndex(t, dirs); err != nil {
return err
}
}
@@ -307,8 +310,17 @@ func (w *Worktree) Reset(opts *ResetOptions) error {
return nil
}
-func (w *Worktree) resetIndex(t *object.Tree) error {
+// Reset the worktree to a specified state.
+func (w *Worktree) Reset(opts *ResetOptions) error {
+ return w.ResetSparsely(opts, nil)
+}
+
+func (w *Worktree) resetIndex(t *object.Tree, dirs []string) error {
idx, err := w.r.Storer.Index()
+ if len(dirs) > 0 {
+ idx.SkipUnless(dirs)
+ }
+
if err != nil {
return err
}
diff --git a/worktree_commit_test.go b/worktree_commit_test.go
index 65d4b69..097c6e5 100644
--- a/worktree_commit_test.go
+++ b/worktree_commit_test.go
@@ -212,10 +212,10 @@ func (s *WorktreeSuite) TestCommitTreeSort(c *C) {
defer clean()
st := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
- r, err := Init(st, nil)
+ _, err := Init(st, nil)
c.Assert(err, IsNil)
- r, _ = Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
+ r, _ := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
URL: fs.Root(),
})
@@ -296,6 +296,7 @@ func (s *WorktreeSuite) TestJustStoreObjectsNotAlreadyStored(c *C) {
All: true,
Author: defaultSignature(),
})
+ c.Assert(err, IsNil)
c.Assert(hash, Equals, plumbing.NewHash("97c0c5177e6ac57d10e8ea0017f2d39b91e2b364"))
// Step 3: Check
diff --git a/worktree_test.go b/worktree_test.go
index 79cbefd..4a14126 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -10,6 +10,7 @@ import (
"path/filepath"
"regexp"
"runtime"
+ "strings"
"testing"
"time"
@@ -183,7 +184,7 @@ func (s *WorktreeSuite) TestPullInSingleBranch(c *C) {
c.Assert(err, IsNil)
c.Assert(branch.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
- branch, err = r.Reference("refs/remotes/foo/branch", false)
+ _, err = r.Reference("refs/remotes/foo/branch", false)
c.Assert(err, NotNil)
storage := r.Storer.(*memory.Storage)
@@ -417,6 +418,37 @@ func (s *WorktreeSuite) TestCheckoutSymlink(c *C) {
c.Assert(err, IsNil)
}
+func (s *WorktreeSuite) TestCheckoutSparse(c *C) {
+ fs := memfs.New()
+ r, err := Clone(memory.NewStorage(), fs, &CloneOptions{
+ URL: s.GetBasicLocalRepositoryURL(),
+ })
+ c.Assert(err, IsNil)
+
+ w, err := r.Worktree()
+ c.Assert(err, IsNil)
+
+ sparseCheckoutDirectories := []string{"go", "json", "php"}
+ c.Assert(w.Checkout(&CheckoutOptions{
+ SparseCheckoutDirectories: sparseCheckoutDirectories,
+ }), IsNil)
+
+ fis, err := fs.ReadDir("/")
+ c.Assert(err, IsNil)
+
+ for _, fi := range fis {
+ c.Assert(fi.IsDir(), Equals, true)
+ var oneOfSparseCheckoutDirs bool
+
+ for _, sparseCheckoutDirectory := range sparseCheckoutDirectories {
+ if strings.HasPrefix(fi.Name(), sparseCheckoutDirectory) {
+ oneOfSparseCheckoutDirs = true
+ }
+ }
+ c.Assert(oneOfSparseCheckoutDirs, Equals, true)
+ }
+}
+
func (s *WorktreeSuite) TestFilenameNormalization(c *C) {
if runtime.GOOS == "windows" {
c.Skip("windows paths may contain non utf-8 sequences")
@@ -555,6 +587,7 @@ func (s *WorktreeSuite) TestCheckoutRelativePathSubmoduleInitialized(c *C) {
// test submodule path
modules, err := w.readGitmodulesFile()
+ c.Assert(err, IsNil)
c.Assert(modules.Submodules["basic"].URL, Equals, "../basic.git")
c.Assert(modules.Submodules["itself"].URL, Equals, "../submodule.git")