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--common_test.go8
-rw-r--r--go.mod7
-rw-r--r--go.sum31
-rw-r--r--options.go23
-rw-r--r--plumbing/format/gitignore/dir.go25
-rw-r--r--plumbing/format/gitignore/dir_test.go17
-rw-r--r--plumbing/protocol/packp/updreq.go6
-rw-r--r--plumbing/protocol/packp/updreq_encode.go18
-rw-r--r--plumbing/protocol/packp/updreq_encode_test.go30
-rw-r--r--remote.go143
-rw-r--r--remote_test.go164
-rw-r--r--worktree.go1
14 files changed, 469 insertions, 43 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/common_test.go b/common_test.go
index 5f5bc4c..b47f5bb 100644
--- a/common_test.go
+++ b/common_test.go
@@ -198,3 +198,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/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/options.go b/options.go
index 7068796..e54889f 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,11 @@ 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
+ // PushOptions sets options to be transferred to the server during push.
+ Options map[string]string
}
// Validate validates the fields and sets the default values.
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/protocol/packp/updreq.go b/plumbing/protocol/packp/updreq.go
index 4d927d8..46ad6fd 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
@@ -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..08a819e 100644
--- a/plumbing/protocol/packp/updreq_encode.go
+++ b/plumbing/protocol/packp/updreq_encode.go
@@ -29,6 +29,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 +79,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..6ba0043 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,29 @@ 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)
+}
diff --git a/remote.go b/remote.go
index 4a06106..426bde9 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,77 @@ 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 {
+ if _, ok := tags[*reference]; ok {
+ 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\n", 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\n", 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 +319,23 @@ func (r *Remote) newReferenceUpdateRequest(
}
}
+ 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 err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune); 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 +404,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
}
@@ -515,6 +609,10 @@ func (r *Remote) addOrUpdateReferences(
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
}
@@ -569,6 +667,43 @@ 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 {
diff --git a/remote_test.go b/remote_test.go
index 1efc9da..df07c08 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())
@@ -903,6 +973,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 +1210,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/worktree.go b/worktree.go
index f23d9f1..362d10e 100644
--- a/worktree.go
+++ b/worktree.go
@@ -73,6 +73,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,