aboutsummaryrefslogtreecommitdiffstats
path: root/_examples/merge_base/main.go
blob: 46725e1a78fd73e622b24aa200c2c86e51dcfc2d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package main

import (
	"os"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/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)
}