Skip to content

Commit ddfe460

Browse files
committed
feat: git change detection works on any commit
1 parent bd666fc commit ddfe460

File tree

7 files changed

+920
-75
lines changed

7 files changed

+920
-75
lines changed

cmd/terramate/cli/cli.go

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -626,22 +626,16 @@ func (c *cli) setupGit() {
626626
return
627627
}
628628

629-
remoteCheckFailed := false
630-
631629
if err := c.prj.checkDefaultRemote(); err != nil {
632630
if c.prj.git.remoteConfigured {
633631
fatal(err, "checking git default remote")
634-
} else {
635-
remoteCheckFailed = true
636632
}
637633
}
638634

639635
if c.parsedArgs.GitChangeBase != "" {
640-
c.prj.baseRef = c.parsedArgs.GitChangeBase
641-
} else if remoteCheckFailed {
642-
c.prj.baseRef = c.prj.defaultLocalBaseRef()
636+
c.prj.baseRev = c.parsedArgs.GitChangeBase
643637
} else {
644-
c.prj.baseRef = c.prj.defaultBaseRef()
638+
c.prj.baseRev = c.prj.selectChangeBase()
645639
}
646640
}
647641

@@ -762,7 +756,7 @@ func (c *cli) triggerStackByFilter() {
762756
fatal(errors.E("trigger command expects either a stack path or the --experimental-status flag"))
763757
}
764758

765-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
759+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
766760
status := parseStatusFilter(c.parsedArgs.Experimental.Trigger.ExperimentalStatus)
767761
stacksReport, err := c.listStacks(mgr, false, status)
768762
if err != nil {
@@ -1321,7 +1315,7 @@ func (c *cli) printStacks() {
13211315
c.fatal("Invalid args", errors.E("the --why flag must be used together with --changed"))
13221316
}
13231317

1324-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1318+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
13251319

13261320
status := parseStatusFilter(c.parsedArgs.List.ExperimentalStatus)
13271321
report, err := c.listStacks(mgr, c.parsedArgs.Changed, status)
@@ -1361,7 +1355,7 @@ func parseStatusFilter(strStatus string) cloudstack.FilterStatus {
13611355
}
13621356

13631357
func (c *cli) printRuntimeEnv() {
1364-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1358+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
13651359
report, err := c.listStacks(mgr, c.parsedArgs.Changed, cloudstack.NoFilter)
13661360
if err != nil {
13671361
fatal(err, "listing stacks")
@@ -1589,7 +1583,7 @@ func (c *cli) generateDebug() {
15891583
}
15901584

15911585
func (c *cli) printStacksGlobals() {
1592-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1586+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
15931587
report, err := c.listStacks(mgr, c.parsedArgs.Changed, cloudstack.NoFilter)
15941588
if err != nil {
15951589
fatal(err, "listing stacks globals: listing stacks")
@@ -1623,7 +1617,7 @@ func (c *cli) printMetadata() {
16231617
Str("action", "cli.printMetadata()").
16241618
Logger()
16251619

1626-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1620+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
16271621
report, err := c.listStacks(mgr, c.parsedArgs.Changed, cloudstack.NoFilter)
16281622
if err != nil {
16291623
fatal(err, "loading metadata: listing stacks")
@@ -1687,7 +1681,7 @@ func (c *cli) checkGenCode() bool {
16871681
}
16881682

16891683
func (c *cli) ensureStackID() {
1690-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1684+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
16911685
report, err := c.listStacks(mgr, false, cloudstack.NoFilter)
16921686
if err != nil {
16931687
fatal(err, "listing stacks")
@@ -1938,7 +1932,7 @@ func (c *cli) friendlyFmtDir(dir string) (string, bool) {
19381932
}
19391933

19401934
func (c *cli) computeSelectedStacks(ensureCleanRepo bool) (config.List[*config.SortableStack], error) {
1941-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1935+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
19421936

19431937
report, err := c.listStacks(mgr, c.parsedArgs.Changed, cloudstack.NoFilter)
19441938
if err != nil {

cmd/terramate/cli/project.go

Lines changed: 43 additions & 54 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

2626
git struct {
@@ -63,25 +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-
logger := log.With().
71-
Str("action", "localDefaultBranchCommit()").
72-
Logger()
73-
74-
gitcfg := p.gitcfg()
75-
refName := gitcfg.DefaultRemote + "/" + gitcfg.DefaultBranch
76-
val, err := p.git.wrapper.RevParse(refName)
77-
if err != nil {
78-
logger.Fatal().Err(err).Send()
79-
}
80-
81-
p.git.localDefaultBranchCommit = val
82-
return val
83-
}
84-
8566
func (p *project) isGitFeaturesEnabled() bool {
8667
return p.isRepo && p.hasCommit()
8768
}
@@ -137,50 +118,58 @@ func (p *project) remoteDefaultCommit() string {
137118
return p.git.remoteDefaultBranchCommit
138119
}
139120

140-
func (p *project) isDefaultBranch() bool {
141-
git := p.gitcfg()
142-
branch, err := p.git.wrapper.CurrentBranch()
143-
if err != nil {
144-
// WHY?
145-
// The current branch name (the symbolic-ref of the HEAD) is not always
146-
// available, in this case we naively check if HEAD == local origin/main.
147-
// This case usually happens in the git setup of CIs.
148-
return p.localDefaultBranchCommit() == p.headCommit()
149-
}
121+
// selectChangeBase returns the revision used for change comparison based on the current Git state.
122+
func (p *project) selectChangeBase() string {
123+
gitcfg := p.gitcfg()
124+
gw := p.git.wrapper
150125

151-
return branch == git.DefaultBranch
152-
}
126+
// Try using remote default branch first
127+
defaultBranchRev, _ := gw.RevParse(gitcfg.DefaultRemote + "/" + gitcfg.DefaultBranch)
128+
if defaultBranchRev == "" {
129+
// Fall back to local default branch
130+
defaultBranchRev, _ = gw.RevParse(gitcfg.DefaultBranch)
153131

154-
// defaultBaseRef returns the baseRef for the current git environment.
155-
func (p *project) defaultBaseRef() string {
156-
git := p.gitcfg()
157-
if p.isDefaultBranch() &&
158-
p.remoteDefaultCommit() == p.headCommit() {
159-
_, err := p.git.wrapper.RevParse(git.DefaultBranchBaseRef)
160-
if err == nil {
161-
return git.DefaultBranchBaseRef
132+
if defaultBranchRev == "" {
133+
// There's no default branch available, so we can't look for a common parent with it.
134+
return gitcfg.DefaultBranchBaseRef
162135
}
163136
}
164137

165-
return p.defaultBranchRef()
166-
}
138+
branch, _ := gw.CurrentBranch()
167139

168-
// defaultLocalBaseRef returns the baseRef in case there's no remote setup.
169-
func (p *project) defaultLocalBaseRef() string {
170-
git := p.gitcfg()
171-
if p.isDefaultBranch() {
172-
_, err := p.git.wrapper.RevParse(git.DefaultBranchBaseRef)
173-
if err == nil {
174-
return git.DefaultBranchBaseRef
140+
// Either we are on a branch or at a detached HEAD.
141+
if branch != "" {
142+
if branch == gitcfg.DefaultBranch {
143+
// We are at the tip of the default branch -> latest default commit.
144+
return gitcfg.DefaultBranchBaseRef
175145
}
146+
147+
// Fallthrough to common parent if not on default branch
148+
} else {
149+
headRev, _ := gw.RevParse("HEAD")
150+
isDetachedDefaultBranchTip := headRev == defaultBranchRev
151+
if isDetachedDefaultBranchTip {
152+
// We are at the latest commit of the default branch.
153+
return gitcfg.DefaultBranchBaseRef
154+
}
155+
156+
isDefaultBranchAncestor, _ := gw.IsFirstParentAncestor("HEAD", defaultBranchRev)
157+
if isDefaultBranchAncestor {
158+
// We are at an older commit of the default branch.
159+
return gitcfg.DefaultBranchBaseRef
160+
}
161+
162+
// Fallthrough to common parent if not at commit of default branch
176163
}
177164

178-
return git.DefaultBranch
179-
}
165+
commonParentWithDefaultBranch, _ := gw.FindNearestCommonParent(defaultBranchRev, "HEAD")
166+
if commonParentWithDefaultBranch != "" {
167+
// We have a nearest common parent with the default branch. Similar to the historic merge base.
168+
return commonParentWithDefaultBranch
169+
}
180170

181-
func (p project) defaultBranchRef() string {
182-
git := p.gitcfg()
183-
return git.DefaultRemote + "/" + git.DefaultBranch
171+
// Fall back to default. Should never happen unless running on an isolated commit.
172+
return gitcfg.DefaultBranchBaseRef
184173
}
185174

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

0 commit comments

Comments
 (0)