diff options
Diffstat (limited to '_examples')
-rw-r--r-- | _examples/common_test.go | 13 | ||||
-rw-r--r-- | _examples/log/main.go | 5 | ||||
-rw-r--r-- | _examples/ls-remote/main.go | 42 | ||||
-rw-r--r-- | _examples/merge_base/help-long.msg.go | 63 | ||||
-rw-r--r-- | _examples/merge_base/helpers.go | 61 | ||||
-rw-r--r-- | _examples/merge_base/main.go | 124 | ||||
-rw-r--r-- | _examples/open/main.go | 2 | ||||
-rw-r--r-- | _examples/progress/main.go | 2 | ||||
-rw-r--r-- | _examples/pull/main.go | 2 | ||||
-rw-r--r-- | _examples/tag/main.go | 2 |
10 files changed, 305 insertions, 11 deletions
diff --git a/_examples/common_test.go b/_examples/common_test.go index 47463a1..89d49a3 100644 --- a/_examples/common_test.go +++ b/_examples/common_test.go @@ -29,6 +29,7 @@ var args = map[string][]string{ "tag": {cloneRepository(defaultURL, tempFolder())}, "pull": {createRepositoryWithRemote(tempFolder(), defaultURL)}, "ls": {cloneRepository(defaultURL, tempFolder()), "HEAD", "vendor"}, + "merge_base": {cloneRepository(defaultURL, tempFolder()), "--is-ancestor", "HEAD~3", "HEAD^"}, } var ignored = map[string]bool{} @@ -50,14 +51,15 @@ func TestExamples(t *testing.T) { } for _, example := range examples { - _, name := filepath.Split(filepath.Dir(example)) + dir := filepath.Dir(example) + _, name := filepath.Split(dir) if ignored[name] { continue } t.Run(name, func(t *testing.T) { - testExample(t, name, example) + testExample(t, name, dir) }) } } @@ -135,10 +137,9 @@ func addRemote(local, remote string) { CheckIfError(err) } -func testExample(t *testing.T, name, example string) { - cmd := exec.Command("go", append([]string{ - "run", filepath.Join(example), - }, args[name]...)...) +func testExample(t *testing.T, name, dir string) { + arguments := append([]string{"run", dir}, args[name]...) + cmd := exec.Command("go", arguments...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/_examples/log/main.go b/_examples/log/main.go index ba0597a..5807515 100644 --- a/_examples/log/main.go +++ b/_examples/log/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "time" "gopkg.in/src-d/go-git.v4" . "gopkg.in/src-d/go-git.v4/_examples" @@ -31,7 +32,9 @@ func main() { CheckIfError(err) // ... retrieves the commit history - cIter, err := r.Log(&git.LogOptions{From: ref.Hash()}) + since := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) + until := time.Date(2019, 7, 30, 0, 0, 0, 0, time.UTC) + cIter, err := r.Log(&git.LogOptions{From: ref.Hash(), Since: &since, Until: &until}) CheckIfError(err) // ... just iterates over the commits, printing it diff --git a/_examples/ls-remote/main.go b/_examples/ls-remote/main.go new file mode 100644 index 0000000..42d9b4e --- /dev/null +++ b/_examples/ls-remote/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "log" + + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/config" + "gopkg.in/src-d/go-git.v4/storage/memory" +) + +// Retrieve remote tags without cloning repository +func main() { + + // Create the remote with repository URL + rem := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{ + Name: "origin", + URLs: []string{"https://github.com/Zenika/MARCEL"}, + }) + + log.Print("Fetching tags...") + + // We can then use every Remote functions to retrieve wanted information + refs, err := rem.List(&git.ListOptions{}) + if err != nil { + log.Fatal(err) + } + + // Filters the references list and only keeps tags + var tags []string + for _, ref := range refs { + if ref.Name().IsTag() { + tags = append(tags, ref.Name().Short()) + } + } + + if len(tags) == 0 { + log.Println("No tags!") + return + } + + log.Printf("Tags found: %v", tags) +} diff --git a/_examples/merge_base/help-long.msg.go b/_examples/merge_base/help-long.msg.go new file mode 100644 index 0000000..7759cbd --- /dev/null +++ b/_examples/merge_base/help-long.msg.go @@ -0,0 +1,63 @@ +package main + +const helpLongMsg = ` +NAME: + %_COMMAND_NAME_% - Lists the best common ancestors of the two passed commit revisions + +SYNOPSIS: + usage: %_COMMAND_NAME_% <path> <commitRev> <commitRev> + or: %_COMMAND_NAME_% <path> --independent <commitRev>... + or: %_COMMAND_NAME_% <path> --is-ancestor <commitRev> <commitRev> + + params: + <path> Path to the git repository + <commitRev> Git revision as supported by go-git + +DESCRIPTION: + %_COMMAND_NAME_% finds the best common ancestor(s) between two commits. One common ancestor is better than another common ancestor if the latter is an ancestor of the former. + A common ancestor that does not have any better common ancestor is a best common ancestor, i.e. a merge base. Note that there can be more than one merge base for a pair of commits. + Commits that does not share a common history has no common ancestors. + +OPTIONS: + As the most common special case, specifying only two commits on the command line means computing the merge base between the given two commits. + If there is no shared history between the passed commits, there won't be a merge-base, and the command will exit with status 1. + +--independent + List the subgroup from the passed commits, that cannot be reached from any other of the passed ones. In other words, it prints a minimal subset of the supplied commits with the same ancestors. + +--is-ancestor + Check if the first commit is an ancestor of the second one, and exit with status 0 if true, or with status 1 if not. Errors are signaled by a non-zero status that is not 1. + +DISCUSSION: + Given two commits A and B, %_COMMAND_NAME_% A B will output a commit which is the best common ancestor of both, what means that is reachable from both A and B through the parent relationship. + + For example, with this topology: + + o---o---o---o---B + / / + ---3---2---o---1---o---A + + the merge base between A and B is 1. + + With the given topology 2 and 3 are also common ancestors of A and B, but they are not the best ones because they can be also reached from 1. + + When the history involves cross-cross merges, there can be more than one best common ancestor for two commits. For example, with this topology: + + ---1---o---A + \ / + X + / \ + ---2---o---o---B + + When the history involves feature branches depending on other feature branches there can be also more than one common ancestor. For example: + + + o---o---o + / \ + 1---o---A \ + / / \ + ---o---o---2---o---o---B + + In both examples, both 1 and 2 are merge-bases of A and B for each situation. + Neither one is better than the other (both are best merge bases) because 1 cannot be reached from 2, nor the opposite. +` diff --git a/_examples/merge_base/helpers.go b/_examples/merge_base/helpers.go new file mode 100644 index 0000000..179a817 --- /dev/null +++ b/_examples/merge_base/helpers.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "gopkg.in/src-d/go-git.v4/plumbing/object" +) + +func checkIfError(err error, code exitCode, mainReason string, v ...interface{}) { + if err == nil { + return + } + + printErr(wrapErr(err, mainReason, v...)) + os.Exit(int(code)) +} + +func helpAndExit(s string, helpMsg string, code exitCode) { + if code == exitCodeSuccess { + printMsg("%s", s) + } else { + printErr(fmt.Errorf(s)) + } + + fmt.Println(strings.Replace(helpMsg, "%_COMMAND_NAME_%", os.Args[0], -1)) + + os.Exit(int(code)) +} + +func printErr(err error) { + fmt.Printf("\x1b[31;1m%s\x1b[0m\n", fmt.Sprintf("error: %s", err)) +} + +func printMsg(format string, args ...interface{}) { + fmt.Printf("%s\n", fmt.Sprintf(format, args...)) +} + +func printCommits(commits []*object.Commit) { + for _, commit := range commits { + if os.Getenv("LOG_LEVEL") == "verbose" { + fmt.Printf( + "\x1b[36;1m%s \x1b[90;21m%s\x1b[0m %s\n", + commit.Hash.String()[:7], + commit.Hash.String(), + strings.Split(commit.Message, "\n")[0], + ) + } else { + fmt.Println(commit.Hash.String()) + } + } +} + +func wrapErr(err error, s string, v ...interface{}) error { + if err != nil { + return fmt.Errorf("%s\n %s", fmt.Sprintf(s, v...), err) + } + + return nil +} diff --git a/_examples/merge_base/main.go b/_examples/merge_base/main.go new file mode 100644 index 0000000..fe6abc6 --- /dev/null +++ b/_examples/merge_base/main.go @@ -0,0 +1,124 @@ +package main + +import ( + "os" + + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" +) + +type exitCode int + +const ( + exitCodeSuccess exitCode = iota + exitCodeNotFound + exitCodeWrongSyntax + exitCodeCouldNotOpenRepository + exitCodeCouldNotParseRevision + exitCodeUnexpected + + cmdDesc = "Returns the merge-base between two commits:" + + helpShortMsg = ` + usage: %_COMMAND_NAME_% <path> <commitRev> <commitRev> + or: %_COMMAND_NAME_% <path> --independent <commitRev>... + or: %_COMMAND_NAME_% <path> --is-ancestor <commitRev> <commitRev> + or: %_COMMAND_NAME_% --help + + params: + <path> path to the git repository + <commitRev> git revision as supported by go-git + +options: + (no options) lists the best common ancestors of the two passed commits + --independent list commits not reachable from the others + --is-ancestor is the first one ancestor of the other? + --help show the full help message of %_COMMAND_NAME_% +` +) + +// Command that mimics `git merge-base --all <baseRev> <headRev>` +// Command that mimics `git merge-base --is-ancestor <baseRev> <headRev>` +// Command that mimics `git merge-base --independent <commitRev>...` +func main() { + if len(os.Args) == 1 { + helpAndExit("Returns the merge-base between two commits:", helpShortMsg, exitCodeSuccess) + } + + if os.Args[1] == "--help" || os.Args[1] == "-h" { + helpAndExit("Returns the merge-base between two commits:", helpLongMsg, exitCodeSuccess) + } + + if len(os.Args) < 4 { + helpAndExit("Wrong syntax", helpShortMsg, exitCodeWrongSyntax) + } + + path := os.Args[1] + + var modeIndependent, modeAncestor bool + var commitRevs []string + var res []*object.Commit + + switch os.Args[2] { + case "--independent": + modeIndependent = true + commitRevs = os.Args[3:] + case "--is-ancestor": + modeAncestor = true + commitRevs = os.Args[3:] + if len(commitRevs) != 2 { + helpAndExit("Wrong syntax", helpShortMsg, exitCodeWrongSyntax) + } + default: + commitRevs = os.Args[2:] + if len(commitRevs) != 2 { + helpAndExit("Wrong syntax", helpShortMsg, exitCodeWrongSyntax) + } + } + + // Open a git repository from current directory + repo, err := git.PlainOpen(path) + checkIfError(err, exitCodeCouldNotOpenRepository, "not in a git repository") + + // Get the hashes of the passed revisions + var hashes []*plumbing.Hash + for _, rev := range commitRevs { + hash, err := repo.ResolveRevision(plumbing.Revision(rev)) + checkIfError(err, exitCodeCouldNotParseRevision, "could not parse revision '%s'", rev) + hashes = append(hashes, hash) + } + + // Get the commits identified by the passed hashes + var commits []*object.Commit + for _, hash := range hashes { + commit, err := repo.CommitObject(*hash) + checkIfError(err, exitCodeUnexpected, "could not find commit '%s'", hash.String()) + commits = append(commits, commit) + } + + if modeAncestor { + isAncestor, err := commits[0].IsAncestor(commits[1]) + checkIfError(err, exitCodeUnexpected, "could not traverse the repository history") + + if !isAncestor { + os.Exit(int(exitCodeNotFound)) + } + + os.Exit(int(exitCodeSuccess)) + } + + if modeIndependent { + res, err = object.Independents(commits) + checkIfError(err, exitCodeUnexpected, "could not traverse the repository history") + } else { + res, err = commits[0].MergeBase(commits[1]) + checkIfError(err, exitCodeUnexpected, "could not traverse the repository history") + + if len(res) == 0 { + os.Exit(int(exitCodeNotFound)) + } + } + + printCommits(res) +} diff --git a/_examples/open/main.go b/_examples/open/main.go index dec183e..71c6b84 100644 --- a/_examples/open/main.go +++ b/_examples/open/main.go @@ -14,7 +14,7 @@ func main() { CheckArgs("<path>") path := os.Args[1] - // We instanciate a new repository targeting the given path (the .git folder) + // We instantiate a new repository targeting the given path (the .git folder) r, err := git.PlainOpen(path) CheckIfError(err) diff --git a/_examples/progress/main.go b/_examples/progress/main.go index 357703c..074430f 100644 --- a/_examples/progress/main.go +++ b/_examples/progress/main.go @@ -22,7 +22,7 @@ func main() { // as git does, when you make a clone, pull or some other operations the // server sends information via the sideband, this information can being - // collected provinding a io.Writer to the CloneOptions options + // collected providing a io.Writer to the CloneOptions options Progress: os.Stdout, }) diff --git a/_examples/pull/main.go b/_examples/pull/main.go index 06369fa..6f258c8 100644 --- a/_examples/pull/main.go +++ b/_examples/pull/main.go @@ -13,7 +13,7 @@ func main() { CheckArgs("<path>") path := os.Args[1] - // We instance\iate a new repository targeting the given path (the .git folder) + // We instantiate a new repository targeting the given path (the .git folder) r, err := git.PlainOpen(path) CheckIfError(err) diff --git a/_examples/tag/main.go b/_examples/tag/main.go index 1e6212b..93192b0 100644 --- a/_examples/tag/main.go +++ b/_examples/tag/main.go @@ -15,7 +15,7 @@ func main() { CheckArgs("<path>") path := os.Args[1] - // We instanciate a new repository targeting the given path (the .git folder) + // We instantiate a new repository targeting the given path (the .git folder) r, err := git.PlainOpen(path) CheckIfError(err) |