@@ -20,7 +20,7 @@ type project struct {
20
20
wd string
21
21
isRepo bool
22
22
root config.Root
23
- baseRef string
23
+ baseRev string
24
24
normalizedRepo string
25
25
26
26
git struct {
@@ -59,25 +59,6 @@ func (p *project) prettyRepo() string {
59
59
return p .normalizedRepo
60
60
}
61
61
62
- func (p * project ) localDefaultBranchCommit () string {
63
- if p .git .localDefaultBranchCommit != "" {
64
- return p .git .localDefaultBranchCommit
65
- }
66
- logger := log .With ().
67
- Str ("action" , "localDefaultBranchCommit()" ).
68
- Logger ()
69
-
70
- gitcfg := p .gitcfg ()
71
- refName := gitcfg .DefaultRemote + "/" + gitcfg .DefaultBranch
72
- val , err := p .git .wrapper .RevParse (refName )
73
- if err != nil {
74
- logger .Fatal ().Err (err ).Send ()
75
- }
76
-
77
- p .git .localDefaultBranchCommit = val
78
- return val
79
- }
80
-
81
62
func (p * project ) headCommit () string {
82
63
if p .git .headCommit != "" {
83
64
return p .git .headCommit
@@ -119,35 +100,84 @@ func (p *project) remoteDefaultCommit() string {
119
100
return p .git .remoteDefaultBranchCommit
120
101
}
121
102
122
- func (p * project ) isDefaultBranch () bool {
123
- git := p .gitcfg ()
124
- branch , err := p .git .wrapper .CurrentBranch ()
125
- if err != nil {
126
- // WHY?
127
- // The current branch name (the symbolic-ref of the HEAD) is not always
128
- // available, in this case we naively check if HEAD == local origin/main.
129
- // This case usually happens in the git setup of CIs.
130
- return p .localDefaultBranchCommit () == p .headCommit ()
103
+ type baseRevMode int
104
+
105
+ const (
106
+ pending baseRevMode = iota
107
+ deployed
108
+ historic
109
+ )
110
+
111
+ // defaultBaseRev returns the revision used for change comparison based on the current Git state.
112
+ func (p * project ) defaultBaseRev () (string , baseRevMode ) {
113
+ // Details:
114
+ // Given origin/main is the default remote/branch, at commit C.
115
+ // We assume C is the state that ran the last deployment. HEAD is at commit H.
116
+ //
117
+ // There's three scenarios, selected if one of the respective cases match, evaluated in order of definition.
118
+ //
119
+ // - Pending changes should be compared to origin/main to find out what has changed since the last deployment.
120
+ //
121
+ // Case 1: H != C and H is not an ancestor of C -- an undeployed, unmerged commit
122
+ // Case 2: H == C and symbolic-ref(HEAD) != main -- a new, yet empty branch (=> no changes yet)
123
+ //
124
+ // - Deployed changes should be compared to the previous deployment to find out what changed.
125
+ // If we assume that every commit on the main branch is a deployment, that means compare to HEAD^.
126
+ //
127
+ // Case 3: H == C -- latest main commit
128
+ // Case 4: H is a first-parent ancestor of main -- previous main commit
129
+ //
130
+ // - Historic changes are all other non-deployed and non-pending, i.e. commits from an already merged and deployed branch.
131
+ // They should be compared to the fork point with origin/main.
132
+ //
133
+ // Case 5: H has a fork point with origin/main -- a merged branch commit
134
+ gitcfg := p .gitcfg ()
135
+ gw := p .git .wrapper
136
+
137
+ remoteDefaultBranchRef := p .remoteDefaultBranchRef ()
138
+ headRev , _ := gw .RevParse ("HEAD" )
139
+ remoteDefaultRev , _ := gw .RevParse (remoteDefaultBranchRef )
140
+
141
+ isRemoteDefaultRev := headRev != "" && headRev == remoteDefaultRev
142
+
143
+ isRemoteDefaultRevAncestor , _ := gw .IsAncestor ("HEAD" , remoteDefaultBranchRef )
144
+ if ! isRemoteDefaultRev && ! isRemoteDefaultRevAncestor {
145
+ // Case 1 (pending)
146
+ return remoteDefaultBranchRef , pending
131
147
}
132
148
133
- return branch == git .DefaultBranch
134
- }
149
+ branch , _ := gw .CurrentBranch ()
150
+ isBranchRef := branch != ""
151
+ isDefaultBranch := isBranchRef && branch == gitcfg .DefaultBranch
152
+ isEmptyPendingBranch := isBranchRef && isRemoteDefaultRev && ! isDefaultBranch
135
153
136
- // defaultBaseRef returns the baseRef for the current git environment.
137
- func (p * project ) defaultBaseRef () string {
138
- git := p .gitcfg ()
139
- if p .isDefaultBranch () &&
140
- p .remoteDefaultCommit () == p .headCommit () {
141
- _ , err := p .git .wrapper .RevParse (git .DefaultBranchBaseRef )
142
- if err == nil {
143
- return git .DefaultBranchBaseRef
144
- }
154
+ if isEmptyPendingBranch {
155
+ // Case 2 (pending)
156
+ return remoteDefaultBranchRef , pending
157
+ }
158
+
159
+ if isRemoteDefaultRev {
160
+ // Case 3 (deployed)
161
+ return gitcfg .DefaultBranchBaseRef , deployed
162
+ }
163
+
164
+ isRemoteDefaultBranchAncestor , _ := gw .IsFirstParentAncestor ("HEAD" , remoteDefaultBranchRef )
165
+ if isRemoteDefaultBranchAncestor {
166
+ // Case 4 (deployed)
167
+ return gitcfg .DefaultBranchBaseRef , deployed
168
+ }
169
+
170
+ forkPoint , _ := gw .FindForkPoint (remoteDefaultBranchRef , "HEAD" )
171
+ if forkPoint != "" {
172
+ // Case 5 (historic)
173
+ return forkPoint , historic
145
174
}
146
175
147
- return p .defaultBranchRef ()
176
+ // Fallback to pending strategy
177
+ return remoteDefaultBranchRef , pending
148
178
}
149
179
150
- func (p project ) defaultBranchRef () string {
180
+ func (p project ) remoteDefaultBranchRef () string {
151
181
git := p .gitcfg ()
152
182
return git .DefaultRemote + "/" + git .DefaultBranch
153
183
}
0 commit comments