@@ -59,25 +59,6 @@ func (p *project) prettyRepo() string {
5959 return p .normalizedRepo
6060}
6161
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-
8162func (p * project ) headCommit () string {
8263 if p .git .headCommit != "" {
8364 return p .git .headCommit
@@ -119,35 +100,76 @@ func (p *project) remoteDefaultCommit() string {
119100 return p .git .remoteDefaultBranchCommit
120101}
121102
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+ // defaultBaseRev returns the revision used for change comparison based on the current Git state.
104+ func (p * project ) defaultBaseRev () string {
105+ // Details:
106+ // Given origin/main is the default remote/branch, at commit C.
107+ // We assume C is the state that ran the last deployment. HEAD is at commit H.
108+ //
109+ // There's three scenarios, selected if one of the respective cases match, evaluated in order of definition.
110+ //
111+ // - Pending changes should be compared to origin/main to find out what has changed since the last deployment.
112+ //
113+ // Case 1: H != C and H is not an ancestor of C -- an undeployed, unmerged commit
114+ // Case 2: H == C and symbolic-ref(HEAD) != main -- a new, yet empty branch (=> no changes yet)
115+ //
116+ // - Deployed changes should be compared to the previous deployment to find out what changed.
117+ // If we assume that every commit on the main branch is a deployment, that means compare to HEAD^.
118+ //
119+ // Case 3: H == C -- latest main commit
120+ // Case 4: H is a first-parent ancestor of main -- previous main commit
121+ //
122+ // - Historic changes are all other non-deployed and non-pending, i.e. commits from an already merged and deployed branch.
123+ // They should be compared to the fork point with origin/main.
124+ //
125+ // Case 5: H has a fork point with origin/main -- a merged branch commit
126+ gitcfg := p .gitcfg ()
127+ gw := p .git .wrapper
128+
129+ remoteDefaultBranchRef := p .remoteDefaultBranchRef ()
130+ headRev , _ := gw .RevParse ("HEAD" )
131+ remoteDefaultRev , _ := gw .RevParse (remoteDefaultBranchRef )
132+
133+ isRemoteDefaultRev := headRev != "" && headRev == remoteDefaultRev
134+
135+ isRemoteDefaultRevAncestor , _ := gw .IsAncestor ("HEAD" , remoteDefaultBranchRef )
136+ if ! isRemoteDefaultRev && ! isRemoteDefaultRevAncestor {
137+ // Case 1 (pending)
138+ return remoteDefaultBranchRef
131139 }
132140
133- return branch == git .DefaultBranch
134- }
141+ branch , _ := gw .CurrentBranch ()
142+ isBranchRef := branch != ""
143+ isDefaultBranch := isBranchRef && branch == gitcfg .DefaultBranch
144+ isEmptyPendingBranch := isBranchRef && isRemoteDefaultRev && ! isDefaultBranch
135145
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- }
146+ if isEmptyPendingBranch {
147+ // Case 2 (pending)
148+ return remoteDefaultBranchRef
149+ }
150+
151+ if isRemoteDefaultRev {
152+ // Case 3 (deployed)
153+ return gitcfg .DefaultBranchBaseRef
154+ }
155+
156+ isRemoteDefaultBranchAncestor , _ := gw .IsFirstParentAncestor ("HEAD" , remoteDefaultBranchRef )
157+ if isRemoteDefaultBranchAncestor {
158+ // Case 4 (deployed)
159+ return gitcfg .DefaultBranchBaseRef
160+ }
161+
162+ forkPoint , _ := gw .FindForkPoint (remoteDefaultBranchRef , "HEAD" )
163+ if forkPoint != "" {
164+ // Case 5 (historic)
165+ return forkPoint
145166 }
146167
147- return p .defaultBranchRef ()
168+ // Fallback to deployed strategy
169+ return gitcfg .DefaultBranchBaseRef
148170}
149171
150- func (p project ) defaultBranchRef () string {
172+ func (p project ) remoteDefaultBranchRef () string {
151173 git := p .gitcfg ()
152174 return git .DefaultRemote + "/" + git .DefaultBranch
153175}
0 commit comments