Skip to content

Commit d6d7d1d

Browse files
committed
feat: git change detection works on any commit
1 parent d549213 commit d6d7d1d

File tree

6 files changed

+702
-70
lines changed

6 files changed

+702
-70
lines changed

cmd/terramate/cli/cli.go

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

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

640636
if c.parsedArgs.GitChangeBase != "" {
641-
c.prj.baseRef = c.parsedArgs.GitChangeBase
642-
} else if remoteCheckFailed {
643-
c.prj.baseRef = c.prj.defaultLocalBaseRef()
637+
c.prj.baseRev = c.parsedArgs.GitChangeBase
644638
} else {
645-
c.prj.baseRef = c.prj.defaultBaseRef()
639+
c.prj.baseRev = c.prj.selectChangeBase()
646640
}
647641
}
648642

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

766-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
760+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
767761
status := parseStatusFilter(c.parsedArgs.Experimental.Trigger.ExperimentalStatus)
768762
stacksReport, err := c.listStacks(mgr, false, status)
769763
if err != nil {
@@ -1311,7 +1305,7 @@ func (c *cli) printStacks() {
13111305
log.Fatal().Msg("the --why flag must be used together with --changed")
13121306
}
13131307

1314-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1308+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
13151309

13161310
status := parseStatusFilter(c.parsedArgs.List.ExperimentalStatus)
13171311
report, err := c.listStacks(mgr, c.parsedArgs.Changed, status)
@@ -1351,7 +1345,7 @@ func parseStatusFilter(strStatus string) cloudstack.FilterStatus {
13511345
}
13521346

13531347
func (c *cli) printRunEnv() {
1354-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1348+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
13551349
report, err := c.listStacks(mgr, c.parsedArgs.Changed, cloudstack.NoFilter)
13561350
if err != nil {
13571351
fatal(err, "listing stacks")
@@ -1579,7 +1573,7 @@ func (c *cli) generateDebug() {
15791573
}
15801574

15811575
func (c *cli) printStacksGlobals() {
1582-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1576+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
15831577
report, err := c.listStacks(mgr, c.parsedArgs.Changed, cloudstack.NoFilter)
15841578
if err != nil {
15851579
fatal(err, "listing stacks globals: listing stacks")
@@ -1613,7 +1607,7 @@ func (c *cli) printMetadata() {
16131607
Str("action", "cli.printMetadata()").
16141608
Logger()
16151609

1616-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1610+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
16171611
report, err := c.listStacks(mgr, c.parsedArgs.Changed, cloudstack.NoFilter)
16181612
if err != nil {
16191613
fatal(err, "loading metadata: listing stacks")
@@ -1677,7 +1671,7 @@ func (c *cli) checkGenCode() bool {
16771671
}
16781672

16791673
func (c *cli) ensureStackID() {
1680-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1674+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
16811675
report, err := c.listStacks(mgr, false, cloudstack.NoFilter)
16821676
if err != nil {
16831677
fatal(err, "listing stacks")
@@ -1922,7 +1916,7 @@ func (c *cli) friendlyFmtDir(dir string) (string, bool) {
19221916
}
19231917

19241918
func (c *cli) computeSelectedStacks(ensureCleanRepo bool) (config.List[*config.SortableStack], error) {
1925-
mgr := stack.NewManager(c.cfg(), c.prj.baseRef)
1919+
mgr := stack.NewManager(c.cfg(), c.prj.baseRev)
19261920

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

cmd/terramate/cli/project.go

Lines changed: 34 additions & 55 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,48 @@ 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()
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
125+
126+
defaultBranchRev, _ := gw.RevParse(gitcfg.DefaultBranch)
127+
if defaultBranchRev == "" {
128+
// There's no default branch available, so we can't look for a common parent with it.
129+
return gitcfg.DefaultBranchBaseRef
149130
}
150131

151-
return branch == git.DefaultBranch
152-
}
132+
branch, _ := gw.CurrentBranch()
153133

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
134+
// Either we are on a branch or at a detached HEAD.
135+
if branch != "" {
136+
if branch == gitcfg.DefaultBranch {
137+
// We are at the tip of the default branch -> latest default commit.
138+
return gitcfg.DefaultBranchBaseRef
139+
}
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.
145+
return gitcfg.DefaultBranchBaseRef
162146
}
163-
}
164-
165-
return p.defaultBranchRef()
166-
}
167147

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
148+
isDefaultBranchAncestor, _ := gw.IsFirstParentAncestor("HEAD", defaultBranchRev)
149+
if isDefaultBranchAncestor {
150+
// We are at an older commit of the default branch.
151+
return gitcfg.DefaultBranchBaseRef
175152
}
176153
}
177154

178-
return git.DefaultBranch
179-
}
155+
commonParentWithDefaultBranch, _ := gw.FindNearestCommonParent(defaultBranchRev, "HEAD")
156+
if commonParentWithDefaultBranch != "" {
157+
// We have a nearest common parent with the default branch. Similiar to the historic merge base.
158+
return commonParentWithDefaultBranch
159+
}
180160

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

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

0 commit comments

Comments
 (0)