1package main
2
3import (
4 "os"
5
6 "github.com/go-git/go-git/v5"
7 "github.com/go-git/go-git/v5/plumbing"
8 "github.com/go-git/go-git/v5/plumbing/object"
9)
10
11type exitCode int
12
13const (
14 exitCodeSuccess exitCode = iota
15 exitCodeNotFound
16 exitCodeWrongSyntax
17 exitCodeCouldNotOpenRepository
18 exitCodeCouldNotParseRevision
19 exitCodeUnexpected
20
21 cmdDesc = "Returns the merge-base between two commits:"
22
23 helpShortMsg = `
24 usage: %_COMMAND_NAME_% <path> <commitRev> <commitRev>
25 or: %_COMMAND_NAME_% <path> --independent <commitRev>...
26 or: %_COMMAND_NAME_% <path> --is-ancestor <commitRev> <commitRev>
27 or: %_COMMAND_NAME_% --help
28
29 params:
30 <path> path to the git repository
31 <commitRev> git revision as supported by go-git
32
33options:
34 (no options) lists the best common ancestors of the two passed commits
35 --independent list commits not reachable from the others
36 --is-ancestor is the first one ancestor of the other?
37 --help show the full help message of %_COMMAND_NAME_%
38`
39)
40
41// Command that mimics `git merge-base --all <baseRev> <headRev>`
42// Command that mimics `git merge-base --is-ancestor <baseRev> <headRev>`
43// Command that mimics `git merge-base --independent <commitRev>...`
44func main() {
45 if len(os.Args) == 1 {
46 helpAndExit("Returns the merge-base between two commits:", helpShortMsg, exitCodeSuccess)
47 }
48
49 if os.Args[1] == "--help" || os.Args[1] == "-h" {
50 helpAndExit("Returns the merge-base between two commits:", helpLongMsg, exitCodeSuccess)
51 }
52
53 if len(os.Args) < 4 {
54 helpAndExit("Wrong syntax", helpShortMsg, exitCodeWrongSyntax)
55 }
56
57 path := os.Args[1]
58
59 var modeIndependent, modeAncestor bool
60 var commitRevs []string
61 var res []*object.Commit
62
63 switch os.Args[2] {
64 case "--independent":
65 modeIndependent = true
66 commitRevs = os.Args[3:]
67 case "--is-ancestor":
68 modeAncestor = true
69 commitRevs = os.Args[3:]
70 if len(commitRevs) != 2 {
71 helpAndExit("Wrong syntax", helpShortMsg, exitCodeWrongSyntax)
72 }
73 default:
74 commitRevs = os.Args[2:]
75 if len(commitRevs) != 2 {
76 helpAndExit("Wrong syntax", helpShortMsg, exitCodeWrongSyntax)
77 }
78 }
79
80 // Open a git repository from current directory
81 repo, err := git.PlainOpen(path)
82 checkIfError(err, exitCodeCouldNotOpenRepository, "not in a git repository")
83
84 // Get the hashes of the passed revisions
85 var hashes []*plumbing.Hash
86 for _, rev := range commitRevs {
87 hash, err := repo.ResolveRevision(plumbing.Revision(rev))
88 checkIfError(err, exitCodeCouldNotParseRevision, "could not parse revision '%s'", rev)
89 hashes = append(hashes, hash)
90 }
91
92 // Get the commits identified by the passed hashes
93 var commits []*object.Commit
94 for _, hash := range hashes {
95 commit, err := repo.CommitObject(*hash)
96 checkIfError(err, exitCodeUnexpected, "could not find commit '%s'", hash.String())
97 commits = append(commits, commit)
98 }
99
100 if modeAncestor {
101 isAncestor, err := commits[0].IsAncestor(commits[1])
102 checkIfError(err, exitCodeUnexpected, "could not traverse the repository history")
103
104 if !isAncestor {
105 os.Exit(int(exitCodeNotFound))
106 }
107
108 os.Exit(int(exitCodeSuccess))
109 }
110
111 if modeIndependent {
112 res, err = object.Independents(commits)
113 checkIfError(err, exitCodeUnexpected, "could not traverse the repository history")
114 } else {
115 res, err = commits[0].MergeBase(commits[1])
116 checkIfError(err, exitCodeUnexpected, "could not traverse the repository history")
117
118 if len(res) == 0 {
119 os.Exit(int(exitCodeNotFound))
120 }
121 }
122
123 printCommits(res)
124}