From b928b1fafefe6022abdfa78790c7c233411b9516 Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 26 Nov 2017 01:12:38 +0100 Subject: [PATCH 1/9] Fix branch and tag resolver Branch and tag were parsed properly by internal revision parser but not resolved correctly into a git hash cause actual reference parser is based on "absolute path" like for instance to reach a branch named test1 it would be refs/heads/test1, the canonical way of resolving such a revision was not implemented --- repository.go | 26 ++++++++++++++++++++------ repository_test.go | 38 +++++++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/repository.go b/repository.go index 7cdc0d52c..8cdcc0b7e 100644 --- a/repository.go +++ b/repository.go @@ -905,15 +905,29 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err for _, item := range items { switch item.(type) { case revision.Ref: - ref, err := storer.ResolveReference(r.Storer, plumbing.ReferenceName(item.(revision.Ref))) - - if err != nil { - return &plumbing.ZeroHash, err + revisionRef := item.(revision.Ref) + var ref *plumbing.Reference + + for _, rule := range []string{ + "%s", + "refs/%s", + "refs/tags/%s", + "refs/heads/%s", + "refs/remotes/%s", + "refs/remotes/%s/HEAD", + } { + ref, err = storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef))) + + if err == nil { + break + } } - h := ref.Hash() + if ref == nil { + return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound + } - commit, err = r.CommitObject(h) + commit, err = r.CommitObject(ref.Hash()) if err != nil { return &plumbing.ZeroHash, err diff --git a/repository_test.go b/repository_test.go index 2ebc5977d..f9289fc86 100644 --- a/repository_test.go +++ b/repository_test.go @@ -1265,24 +1265,32 @@ func (s *RepositorySuite) TestWorktreeBare(c *C) { } func (s *RepositorySuite) TestResolveRevision(c *C) { - url := s.GetLocalRepositoryURL( - fixtures.ByURL("https://github.com/git-fixtures/basic.git").One(), - ) - - r, _ := Init(memory.NewStorage(), nil) - err := r.clone(context.Background(), &CloneOptions{URL: url}) + f := fixtures.ByURL("https://github.com/git-fixtures/basic.git").One() + sto, err := filesystem.NewStorage(f.DotGit()) + c.Assert(err, IsNil) + r, err := Open(sto, f.DotGit()) c.Assert(err, IsNil) datas := map[string]string{ - "HEAD": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", - "refs/heads/master~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", - "HEAD~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", - "HEAD~3^2": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", - "HEAD~3^2^0": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", - "HEAD~2^{/binary file}": "35e85108805c84807bc66a02d91535e1e24b38b9", - "HEAD~^{!-some}": "1669dce138d9b841a518c64b10914d88f5e488ea", - "HEAD@{2015-03-31T11:56:18Z}": "918c48b83bd081e863dbe1b80f8998f058cd8294", - "HEAD@{2015-03-31T11:49:00Z}": "1669dce138d9b841a518c64b10914d88f5e488ea", + "HEAD": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "heads/master": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "heads/master~1": "918c48b83bd081e863dbe1b80f8998f058cd8294", + "refs/heads/master": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "refs/heads/master~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", + "refs/tags/v1.0.0": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "refs/remotes/origin/master": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "refs/remotes/origin/HEAD": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "HEAD~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", + "HEAD~3^2": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", + "HEAD~3^2^0": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", + "HEAD~2^{/binary file}": "35e85108805c84807bc66a02d91535e1e24b38b9", + "HEAD~^{/!-some}": "1669dce138d9b841a518c64b10914d88f5e488ea", + "master": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "branch": "e8d3ffab552895c19b9fcf7aa264d277cde33881", + "v1.0.0": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "branch~1": "918c48b83bd081e863dbe1b80f8998f058cd8294", + "v1.0.0~1": "918c48b83bd081e863dbe1b80f8998f058cd8294", + "master~1": "918c48b83bd081e863dbe1b80f8998f058cd8294", } for rev, hash := range datas { From 11f9898b2473610b483ad4e3b6fc4f7f1dfc0a5f Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 27 Nov 2017 23:23:53 +0100 Subject: [PATCH 2/9] Add ResolveRevision example --- example_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/example_test.go b/example_test.go index e9d8e8b46..6faba137e 100644 --- a/example_test.go +++ b/example_test.go @@ -85,6 +85,36 @@ func ExampleRepository_References() { } +func ExampleRepository_ResolveRevision() { + r, _ := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{ + URL: "https://github.com/git-fixtures/basic.git", + }) + + // Resolve revision to their corresponding sha + revisions := []string { + "master~1", + "HEAD~2^^~", + "HEAD~2^{/binary file}", + "refs/heads/master~2^^~", + } + + for _, revision := range revisions { + h, err := r.ResolveRevision(plumbing.Revision(revision)) + + if err != nil { + log.Fatalf("%s : %s", revision, err) + } + + fmt.Printf("%s %s\n", h.String(), revision) + } + + // Output: + // 918c48b83bd081e863dbe1b80f8998f058cd8294 master~1 + // b029517f6300c2da0f4b651b8642506cd6aaf45d HEAD~2^^~ + // 35e85108805c84807bc66a02d91535e1e24b38b9 HEAD~2^{/binary file} + // b029517f6300c2da0f4b651b8642506cd6aaf45d refs/heads/master~2^^~ +} + func ExampleRepository_CreateRemote() { r, _ := git.Init(memory.NewStorage(), nil) From f8105197a881d817f5af059d0f975a3c50338fdd Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 27 Nov 2017 23:55:43 +0100 Subject: [PATCH 3/9] Remove wrong implementation --- repository.go | 23 ----------------------- repository_test.go | 7 +++---- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/repository.go b/repository.go index 8cdcc0b7e..70b7e5f86 100644 --- a/repository.go +++ b/repository.go @@ -999,29 +999,6 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err return &plumbing.ZeroHash, fmt.Errorf(`No commit message match regexp : "%s"`, re.String()) } - commit = c - case revision.AtDate: - history := object.NewCommitPreorderIter(commit, nil, nil) - - date := item.(revision.AtDate).Date - - var c *object.Commit - err := history.ForEach(func(hc *object.Commit) error { - if date.Equal(hc.Committer.When.UTC()) || hc.Committer.When.UTC().Before(date) { - c = hc - return storer.ErrStop - } - - return nil - }) - if err != nil { - return &plumbing.ZeroHash, err - } - - if c == nil { - return &plumbing.ZeroHash, fmt.Errorf(`No commit exists prior to date "%s"`, date.String()) - } - commit = c } } diff --git a/repository_test.go b/repository_test.go index f9289fc86..cf0805a84 100644 --- a/repository_test.go +++ b/repository_test.go @@ -1311,10 +1311,9 @@ func (s *RepositorySuite) TestResolveRevisionWithErrors(c *C) { c.Assert(err, IsNil) 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@{2015-03-31T09:49:00Z}": `No commit exists prior to date "2015-03-31 09:49:00 +0000 UTC"`, + "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"`, } for rev, rerr := range datas { From 6be54fd51fd060c64f10c5cb0f58c746a4cdf3a3 Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 28 Nov 2017 23:31:10 +0100 Subject: [PATCH 4/9] Update ResolveRevision documentation --- repository.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/repository.go b/repository.go index 70b7e5f86..9c305f91d 100644 --- a/repository.go +++ b/repository.go @@ -891,6 +891,9 @@ func (r *Repository) Worktree() (*Worktree, error) { } // ResolveRevision resolves revision to corresponding hash. +// +// Implemented resolvers : HEAD, branch, tag, heads/branch, refs/heads/branch, +// refs/tags/tag, refs/remotes/origin/branch, refs/remotes/origin/HEAD, tilde and caret (HEAD~1, master~^, tag~2, ref/heads/master~1, ...), selection by text (HEAD^{/fix nasty bug}) func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error) { p := revision.NewParserFromString(string(rev)) From f1edccc2635c96105a59158a8f0fa208581d33ac Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 1 Dec 2017 14:58:09 +0100 Subject: [PATCH 5/9] Set scope from private to public --- plumbing/reference.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plumbing/reference.go b/plumbing/reference.go index 2c30fe08a..2f53d4e6c 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -15,10 +15,10 @@ const ( symrefPrefix = "ref: " ) -// refRevParseRules are a set of rules to parse references into short names. +// RefRevParseRules are a set of rules to parse references into short names. // These are the same rules as used by git in shorten_unambiguous_ref. // See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417 -var refRevParseRules = []string{ +var RefRevParseRules = []string{ "refs/%s", "refs/tags/%s", "refs/heads/%s", @@ -83,7 +83,7 @@ func (r ReferenceName) String() string { func (r ReferenceName) Short() string { s := string(r) res := s - for _, format := range refRevParseRules { + for _, format := range RefRevParseRules { _, err := fmt.Sscanf(s, format, &res) if err == nil { continue From fe263b84778965e1357366cb960b85fb5a0b7daf Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 1 Dec 2017 14:58:20 +0100 Subject: [PATCH 6/9] Use RefRevParseRules from plubming pkg --- repository.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/repository.go b/repository.go index 9c305f91d..8d4d2c89a 100644 --- a/repository.go +++ b/repository.go @@ -911,14 +911,7 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err revisionRef := item.(revision.Ref) var ref *plumbing.Reference - for _, rule := range []string{ - "%s", - "refs/%s", - "refs/tags/%s", - "refs/heads/%s", - "refs/remotes/%s", - "refs/remotes/%s/HEAD", - } { + for _, rule := range append(plumbing.RefRevParseRules, "%s") { ref, err = storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef))) if err == nil { From 458f0be784a018e8aae7b22475f0a9feb8e58d3c Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 1 Dec 2017 15:21:36 +0100 Subject: [PATCH 7/9] Fix typo --- _examples/common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_examples/common.go b/_examples/common.go index 2719c0ecf..81ed64734 100644 --- a/_examples/common.go +++ b/_examples/common.go @@ -6,7 +6,7 @@ import ( "strings" ) -// CheckArgs should be used to esnure the right command line arguments are +// CheckArgs should be used to ensure the right command line arguments are // passed before executing an example. func CheckArgs(arg ...string) { if len(os.Args) < len(arg)+1 { From eafce48570f3cc48a99b5d73f7db473483de750a Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 1 Dec 2017 15:22:57 +0100 Subject: [PATCH 8/9] Move example to _examples folder --- _examples/README.md | 7 ++++--- _examples/common_test.go | 1 + _examples/revision/main.go | 32 ++++++++++++++++++++++++++++++++ example_test.go | 30 ------------------------------ 4 files changed, 37 insertions(+), 33 deletions(-) create mode 100644 _examples/revision/main.go diff --git a/_examples/README.md b/_examples/README.md index 10594abf9..271379b40 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -11,9 +11,10 @@ Here you can find a list of annotated _go-git_ examples: - [remotes](remotes/main.go) - Working with remotes: adding, removing, etc - [progress](progress/main.go) - Printing the progress information from the sideband - [push](push/main.go) - Push repository to default remote (origin) -- [checkout](checkout/main.go) - check out a specific commit from a repository -- [tag](tag/main.go) - list/print repository tags -- [pull](pull/main.go) - pull changes from a remote repository +- [checkout](checkout/main.go) - Check out a specific commit from a repository +- [tag](tag/main.go) - List/print repository tags +- [pull](pull/main.go) - Pull changes from a remote repository +- [revision](revision/main.go) - Solve a revision into a commit ### Advanced - [custom_http](custom_http/main.go) - Replacing the HTTP client using a custom one - [storage](storage/README.md) - Implementing a custom storage system diff --git a/_examples/common_test.go b/_examples/common_test.go index d164297ea..6f8df458b 100644 --- a/_examples/common_test.go +++ b/_examples/common_test.go @@ -23,6 +23,7 @@ var args = map[string][]string{ "open": {cloneRepository(defaultURL, tempFolder())}, "progress": {defaultURL, tempFolder()}, "push": {setEmptyRemote(cloneRepository(defaultURL, tempFolder()))}, + "revision": {cloneRepository(defaultURL, tempFolder()), "master~2^"}, "showcase": {defaultURL, tempFolder()}, "tag": {cloneRepository(defaultURL, tempFolder())}, "pull": {createRepositoryWithRemote(tempFolder(), defaultURL)}, diff --git a/_examples/revision/main.go b/_examples/revision/main.go new file mode 100644 index 000000000..a2389bd46 --- /dev/null +++ b/_examples/revision/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "os" + + "gopkg.in/src-d/go-git.v4" + . "gopkg.in/src-d/go-git.v4/_examples" + "gopkg.in/src-d/go-git.v4/plumbing" +) + +// Example how to resolve a revision into its commit counterpart +func main() { + CheckArgs("", "") + + path := os.Args[1] + revision := os.Args[2] + + // We instantiate a new repository targeting the given path (the .git folder) + r, err := git.PlainOpen(path) + CheckIfError(err) + + // Resolve revision into a sha1 commit, only some revisions are resolved + // look at the doc to get more details + Info("git rev-parse %s", revision) + + h, err := r.ResolveRevision(plumbing.Revision(revision)) + + CheckIfError(err) + + fmt.Println(h.String()) +} diff --git a/example_test.go b/example_test.go index 6faba137e..e9d8e8b46 100644 --- a/example_test.go +++ b/example_test.go @@ -85,36 +85,6 @@ func ExampleRepository_References() { } -func ExampleRepository_ResolveRevision() { - r, _ := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{ - URL: "https://github.com/git-fixtures/basic.git", - }) - - // Resolve revision to their corresponding sha - revisions := []string { - "master~1", - "HEAD~2^^~", - "HEAD~2^{/binary file}", - "refs/heads/master~2^^~", - } - - for _, revision := range revisions { - h, err := r.ResolveRevision(plumbing.Revision(revision)) - - if err != nil { - log.Fatalf("%s : %s", revision, err) - } - - fmt.Printf("%s %s\n", h.String(), revision) - } - - // Output: - // 918c48b83bd081e863dbe1b80f8998f058cd8294 master~1 - // b029517f6300c2da0f4b651b8642506cd6aaf45d HEAD~2^^~ - // 35e85108805c84807bc66a02d91535e1e24b38b9 HEAD~2^{/binary file} - // b029517f6300c2da0f4b651b8642506cd6aaf45d refs/heads/master~2^^~ -} - func ExampleRepository_CreateRemote() { r, _ := git.Init(memory.NewStorage(), nil) From 1aface6c6b67481f8e762136468f6a1dd811e951 Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 1 Dec 2017 16:09:51 +0100 Subject: [PATCH 9/9] Resolve first simple case --- repository.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repository.go b/repository.go index 8d4d2c89a..8a966c679 100644 --- a/repository.go +++ b/repository.go @@ -911,7 +911,7 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err revisionRef := item.(revision.Ref) var ref *plumbing.Reference - for _, rule := range append(plumbing.RefRevParseRules, "%s") { + for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) { ref, err = storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef))) if err == nil {