-
Notifications
You must be signed in to change notification settings - Fork 247
/
repos.go
171 lines (160 loc) · 5.06 KB
/
repos.go
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//go:build !testUnit && !lint
// +build !testUnit,!lint
// We exclude this file from unit tests and linting because it cannot be
// compiled without CGO and a specific version of libgit2 pre-installed. To keep
// our linting and unit tests lightweight, those are complications we'd like to
// avoid. We'll live without the linting and test this well with integration
// tests.
package main
import (
"log"
"strings"
git "github.com/libgit2/git2go/v32"
"github.com/pkg/errors"
)
// checkout checks out a specific commit, branch, or tag from the provided repo.
// Precedence is given to a specific commit, identified by the provided sha. If
// that value is empty, the provided reference will be used instead. If both are
// empty, the repository's default branch is checked out.
func checkout(repo *git.Repository, sha string, refStr string) error {
// Our first concern is finding the commit in question and while we're at it
// we'll create a local branch to check out into, if necessary.
var commit *git.Commit
var tagRef *git.Reference
var localBranch *git.Branch
var checkingOutDefaultBranch bool
if sha != "" { // Specific commit identified by SHA takes precedence
oid, err := git.NewOid(sha)
if err != nil {
return errors.Wrapf(err, "error getting oid from sha %q", sha)
}
if commit, err = repo.LookupCommit(oid); err != nil {
return errors.Wrapf(err, "error getting commit %q", sha)
}
defer commit.Free()
log.Printf("checking out commit %q", commit.Id().String())
} else if refStr != "" { // Next in order of precedence is a specific ref
ref, err := resolveRef(repo, refStr)
if err != nil {
return errors.Wrapf(err, "error resolving ref %q", refStr)
}
defer ref.Free()
if commit, err = repo.LookupCommit(ref.Target()); err != nil {
return errors.Wrapf(
err,
"error getting commit from ref %q",
ref.Shorthand(),
)
}
defer commit.Free()
if ref.IsRemote() { // i.e. Not a tag
headRef, err := repo.Head()
if err != nil {
return errors.Wrap(err, "error getting ref from default branch")
}
defer headRef.Free()
if ref.Target().String() == headRef.Target().String() {
// This ref points to the default branch, which we already have locally,
// so we'll bypass creating a new local branch.
checkingOutDefaultBranch = true
log.Printf("checking out branch %q", headRef.Shorthand())
} else { // We need to make a new local branch.
// ref.Shorthand() will be like "origin/<branch or tag name>" and we
// want JUST the branch or tag name.
shortName := strings.SplitN(ref.Shorthand(), "/", 2)[1]
if localBranch, err = repo.CreateBranch(shortName, commit, false); err != nil {
return errors.Wrapf(
err,
"error creating branch %q from commit %q",
shortName,
commit.Id().String(),
)
}
defer localBranch.Free()
log.Printf("checking out branch %q", shortName)
}
} else {
tagRef = ref
log.Printf("checking out tag %q", ref.Shorthand())
}
} else { // Last in order of precedence is the default branch
checkingOutDefaultBranch = true
ref, err := repo.Head()
if err != nil {
return errors.Wrap(err, "error getting ref from default branch")
}
defer ref.Free()
if commit, err = repo.LookupCommit(ref.Target()); err != nil {
return errors.Wrap(
err,
"error getting commit from HEAD of default branch",
)
}
log.Printf("checking out branch %q", ref.Shorthand())
}
// This is where we actually perform the checkout
tree, err := repo.LookupTree(commit.TreeId())
if err != nil {
return errors.Wrapf(
err,
"error finding tree for commit %q",
commit.Id().String(),
)
}
defer tree.Free()
if err = repo.CheckoutTree(
tree,
&git.CheckoutOptions{
Strategy: git.CheckoutSafe |
git.CheckoutRecreateMissing |
git.CheckoutAllowConflicts |
git.CheckoutUseTheirs,
},
); err != nil {
return errors.Wrapf(
err,
"error checking out tree for commit %q",
commit.Id().String(),
)
}
// If we just checked out the default branch, we're done.
if checkingOutDefaultBranch {
return nil
}
// If we checked out some other branch, set HEAD to point to the local branch.
if localBranch != nil {
return errors.Wrapf(
repo.SetHead(localBranch.Reference.Name()),
"error setting HEAD to %q",
localBranch.Reference.Name(),
)
}
// If we checked out a tag, we want HEAD detached at the name of the tag.
if tagRef != nil {
return errors.Wrapf(
repo.SetHead(tagRef.Name()),
"error setting detached HEAD to %q",
tagRef.Name(),
)
}
// We must have checked out a specific commit by SHA. We want HEAD detached
// at the SHA.
return errors.Wrapf(
repo.SetHeadDetached(commit.Id()),
"error setting detached HEAD to %q",
commit.Id().String(),
)
}
// initSubmodules iterates over all of the provided repositories submodules and
// initializes and updates each.
func initSubmodules(repo *git.Repository) error {
return repo.Submodules.Foreach(
func(submodule *git.Submodule, name string) error {
return errors.Wrapf(
submodule.Update(true, nil),
"error initializing submodule %q",
name,
)
},
)
}