Skip to content

Commit d3f17fc

Browse files
committed
feat: git change detection works on any commit
1 parent f23d4c0 commit d3f17fc

File tree

7 files changed

+915
-62
lines changed

7 files changed

+915
-62
lines changed

cmd/terramate/cli/cli.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -743,22 +743,16 @@ func (c *cli) setupGit() {
743743
return
744744
}
745745

746-
remoteCheckFailed := false
747-
748746
if err := c.prj.checkDefaultRemote(); err != nil {
749747
if c.prj.git.remoteConfigured {
750748
fatal("checking git default remote", err)
751-
} else {
752-
remoteCheckFailed = true
753749
}
754750
}
755751

756752
if c.parsedArgs.GitChangeBase != "" {
757-
c.prj.baseRef = c.parsedArgs.GitChangeBase
758-
} else if remoteCheckFailed {
759-
c.prj.baseRef = c.prj.defaultLocalBaseRef()
753+
c.prj.baseRev = c.parsedArgs.GitChangeBase
760754
} else {
761-
c.prj.baseRef = c.prj.defaultBaseRef()
755+
c.prj.baseRev = c.prj.selectChangeBase()
762756
}
763757
}
764758

@@ -2175,7 +2169,7 @@ func (c *cli) gitSafeguardRemoteEnabled() bool {
21752169
func (c *cli) wd() string { return c.prj.wd }
21762170
func (c *cli) rootdir() string { return c.prj.rootdir }
21772171
func (c *cli) cfg() *config.Root { return &c.prj.root }
2178-
func (c *cli) baseRef() string { return c.prj.baseRef }
2172+
func (c *cli) baseRef() string { return c.prj.baseRev }
21792173
func (c *cli) stackManager() *stack.Manager { return c.prj.stackManager }
21802174
func (c *cli) rootNode() hcl.Config { return c.prj.root.Tree().Node }
21812175
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-
fatal("unable to git rev-parse", err)
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
}
@@ -125,47 +110,58 @@ func (p *project) remoteDefaultCommit() string {
125110
return p.git.remoteDefaultBranchCommit
126111
}
127112

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

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

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

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

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

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

0 commit comments

Comments
 (0)