Skip to content

Commit 04e123d

Browse files
committed
feat: git change detection works on any commit
1 parent 17fd930 commit 04e123d

File tree

7 files changed

+916
-63
lines changed

7 files changed

+916
-63
lines changed

cmd/terramate/cli/cli.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -766,22 +766,16 @@ func (c *cli) setupGit() {
766766
return
767767
}
768768

769-
remoteCheckFailed := false
770-
771769
if err := c.prj.checkDefaultRemote(); err != nil {
772770
if c.prj.git.remoteConfigured {
773-
fatalWithDetails(err, "checking git default remote")
774-
} else {
775-
remoteCheckFailed = true
771+
fatalWithDetails(err, "checking git default remote", err)
776772
}
777773
}
778774

779775
if c.parsedArgs.GitChangeBase != "" {
780-
c.prj.baseRef = c.parsedArgs.GitChangeBase
781-
} else if remoteCheckFailed {
782-
c.prj.baseRef = c.prj.defaultLocalBaseRef()
776+
c.prj.baseRev = c.parsedArgs.GitChangeBase
783777
} else {
784-
c.prj.baseRef = c.prj.defaultBaseRef()
778+
c.prj.baseRev = c.prj.selectChangeBase()
785779
}
786780
}
787781

@@ -2248,7 +2242,7 @@ func (c *cli) gitSafeguardRemoteEnabled() bool {
22482242
func (c *cli) wd() string { return c.prj.wd }
22492243
func (c *cli) rootdir() string { return c.prj.rootdir }
22502244
func (c *cli) cfg() *config.Root { return &c.prj.root }
2251-
func (c *cli) baseRef() string { return c.prj.baseRef }
2245+
func (c *cli) baseRef() string { return c.prj.baseRev }
22522246
func (c *cli) stackManager() *stack.Manager { return c.prj.stackManager }
22532247
func (c *cli) rootNode() hcl.Config { return c.prj.root.Tree().Node }
22542248
func (c *cli) cred() credential { return c.cloud.client.Credential.(credential) }

cmd/terramate/cli/project.go

Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type project struct {
2020
wd string
2121
isRepo bool
2222
root config.Root
23-
baseRef string
23+
baseRev string
2424
normalizedRepo string
2525
stackManager *stack.Manager
2626

@@ -63,21 +63,6 @@ func (p *project) prettyRepo() string {
6363
return p.normalizedRepo
6464
}
6565

66-
func (p *project) localDefaultBranchCommit() string {
67-
if p.git.localDefaultBranchCommit != "" {
68-
return p.git.localDefaultBranchCommit
69-
}
70-
gitcfg := p.gitcfg()
71-
refName := gitcfg.DefaultRemote + "/" + gitcfg.DefaultBranch
72-
val, err := p.git.wrapper.RevParse(refName)
73-
if err != nil {
74-
fatalWithDetails(err, "unable to git rev-parse")
75-
}
76-
77-
p.git.localDefaultBranchCommit = val
78-
return val
79-
}
80-
8166
func (p *project) isGitFeaturesEnabled() bool {
8267
return p.isRepo && p.hasCommit()
8368
}
@@ -126,47 +111,58 @@ func (p *project) remoteDefaultCommit() string {
126111
return p.git.remoteDefaultBranchCommit
127112
}
128113

129-
func (p *project) isDefaultBranch() bool {
130-
git := p.gitcfg()
131-
branch, err := p.git.wrapper.CurrentBranch()
132-
if err != nil {
133-
// WHY?
134-
// The current branch name (the symbolic-ref of the HEAD) is not always
135-
// available, in this case we naively check if HEAD == local origin/main.
136-
// This case usually happens in the git setup of CIs.
137-
return p.localDefaultBranchCommit() == p.headCommit()
138-
}
114+
// selectChangeBase returns the revision used for change comparison based on the current Git state.
115+
func (p *project) selectChangeBase() string {
116+
gitcfg := p.gitcfg()
117+
gw := p.git.wrapper
139118

140-
return branch == git.DefaultBranch
141-
}
119+
// Try using remote default branch first
120+
defaultBranchRev, _ := gw.RevParse(gitcfg.DefaultRemote + "/" + gitcfg.DefaultBranch)
121+
if defaultBranchRev == "" {
122+
// Fall back to local default branch
123+
defaultBranchRev, _ = gw.RevParse(gitcfg.DefaultBranch)
142124

143-
// defaultBaseRef returns the baseRef for the current git environment.
144-
func (p *project) defaultBaseRef() string {
145-
if p.isDefaultBranch() &&
146-
p.remoteDefaultCommit() == p.headCommit() {
147-
_, err := p.git.wrapper.RevParse(defaultBranchBaseRef)
148-
if err == nil {
125+
if defaultBranchRev == "" {
126+
// There's no default branch available, so we can't look for a common parent with it.
149127
return defaultBranchBaseRef
150128
}
151129
}
152-
return p.defaultBranchRef()
153-
}
154130

155-
// defaultLocalBaseRef returns the baseRef in case there's no remote setup.
156-
func (p *project) defaultLocalBaseRef() string {
157-
git := p.gitcfg()
158-
if p.isDefaultBranch() {
159-
_, err := p.git.wrapper.RevParse(defaultBranchBaseRef)
160-
if err == nil {
131+
branch, _ := gw.CurrentBranch()
132+
133+
// Either we are on a branch or at a detached HEAD.
134+
if branch != "" {
135+
if branch == gitcfg.DefaultBranch {
136+
// We are at the tip of the default branch -> latest default commit.
137+
return defaultBranchBaseRef
138+
}
139+
140+
// Fallthrough to common parent if not on default branch
141+
} else {
142+
headRev, _ := gw.RevParse("HEAD")
143+
isDetachedDefaultBranchTip := headRev == defaultBranchRev
144+
if isDetachedDefaultBranchTip {
145+
// We are at the latest commit of the default branch.
161146
return defaultBranchBaseRef
162147
}
148+
149+
isDefaultBranchAncestor, _ := gw.IsFirstParentAncestor("HEAD", defaultBranchRev)
150+
if isDefaultBranchAncestor {
151+
// We are at an older commit of the default branch.
152+
return defaultBranchBaseRef
153+
}
154+
155+
// Fallthrough to common parent if not at commit of default branch
156+
}
157+
158+
commonParentWithDefaultBranch, _ := gw.FindNearestCommonParent(defaultBranchRev, "HEAD")
159+
if commonParentWithDefaultBranch != "" {
160+
// We have a nearest common parent with the default branch. Similar to the historic merge base.
161+
return commonParentWithDefaultBranch
163162
}
164-
return git.DefaultBranch
165-
}
166163

167-
func (p project) defaultBranchRef() string {
168-
git := p.gitcfg()
169-
return git.DefaultRemote + "/" + git.DefaultBranch
164+
// Fall back to default. Should never happen unless running on an isolated commit.
165+
return defaultBranchBaseRef
170166
}
171167

172168
func (p *project) setDefaults() error {

0 commit comments

Comments
 (0)