@@ -71,15 +71,13 @@ type GetCommitsOptions struct {
71
71
// GetCommits obtains the commits of the current branch
72
72
func (self * CommitLoader ) GetCommits (opts GetCommitsOptions ) ([]* models.Commit , error ) {
73
73
commits := []* models.Commit {}
74
- var rebasingCommits []* models.Commit
75
74
76
75
if opts .IncludeRebaseCommits && opts .FilterPath == "" {
77
76
var err error
78
- rebasingCommits , err = self .MergeRebasingCommits (commits )
77
+ commits , err = self .MergeRebasingCommits (commits )
79
78
if err != nil {
80
79
return nil , err
81
80
}
82
- commits = append (commits , rebasingCommits ... )
83
81
}
84
82
85
83
wg := sync.WaitGroup {}
@@ -126,7 +124,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
126
124
if commit .Hash == firstPushedCommit {
127
125
passedFirstPushedCommit = true
128
126
}
129
- if commit .Status != models . StatusRebasing {
127
+ if ! commit .IsTODO () {
130
128
if passedFirstPushedCommit {
131
129
commit .Status = models .StatusPushed
132
130
} else {
@@ -171,19 +169,26 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
171
169
}
172
170
}
173
171
174
- if ! self .getWorkingTreeState ().Rebasing {
175
- // not in rebase mode so return original commits
176
- return result , nil
172
+ workingTreeState := self .getWorkingTreeState ()
173
+ addConflictedRebasingCommit := true
174
+ if workingTreeState .CherryPicking || workingTreeState .Reverting {
175
+ sequencerCommits , err := self .getHydratedSequencerCommits (workingTreeState )
176
+ if err != nil {
177
+ return nil , err
178
+ }
179
+ result = append (sequencerCommits , result ... )
180
+ addConflictedRebasingCommit = false
177
181
}
178
182
179
- rebasingCommits , err := self .getHydratedRebasingCommits ()
180
- if err != nil {
181
- return nil , err
182
- }
183
- if len (rebasingCommits ) > 0 {
184
- result = append (rebasingCommits , result ... )
183
+ if workingTreeState .Rebasing {
184
+ rebasingCommits , err := self .getHydratedRebasingCommits (addConflictedRebasingCommit )
185
+ if err != nil {
186
+ return nil , err
187
+ }
188
+ if len (rebasingCommits ) > 0 {
189
+ result = append (rebasingCommits , result ... )
190
+ }
185
191
}
186
-
187
192
return result , nil
188
193
}
189
194
@@ -242,14 +247,36 @@ func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool
242
247
}
243
248
}
244
249
245
- func (self * CommitLoader ) getHydratedRebasingCommits () ([]* models.Commit , error ) {
246
- commits := self .getRebasingCommits ()
250
+ func (self * CommitLoader ) getHydratedRebasingCommits (addConflictingCommit bool ) ([]* models.Commit , error ) {
251
+ todoFileHasShortHashes := self .version .IsOlderThan (2 , 25 , 2 )
252
+ return self .getHydratedTodoCommits (self .getRebasingCommits (addConflictingCommit ), todoFileHasShortHashes )
253
+ }
247
254
248
- if len (commits ) == 0 {
255
+ func (self * CommitLoader ) getHydratedSequencerCommits (workingTreeState models.WorkingTreeState ) ([]* models.Commit , error ) {
256
+ commits := self .getSequencerCommits ()
257
+ if len (commits ) > 0 {
258
+ // If we have any commits in .git/sequencer/todo, then the last one of
259
+ // those is the conflicting one.
260
+ commits [len (commits )- 1 ].Status = models .StatusConflicted
261
+ } else {
262
+ // For single-commit cherry-picks and reverts, git apparently doesn't
263
+ // use the sequencer; in that case, CHERRY_PICK_HEAD or REVERT_HEAD is
264
+ // our conflicting commit, so synthesize it here.
265
+ conflicedCommit := self .getConflictedSequencerCommit (workingTreeState )
266
+ if conflicedCommit != nil {
267
+ commits = append (commits , conflicedCommit )
268
+ }
269
+ }
270
+
271
+ return self .getHydratedTodoCommits (commits , true )
272
+ }
273
+
274
+ func (self * CommitLoader ) getHydratedTodoCommits (todoCommits []* models.Commit , todoFileHasShortHashes bool ) ([]* models.Commit , error ) {
275
+ if len (todoCommits ) == 0 {
249
276
return nil , nil
250
277
}
251
278
252
- commitHashes := lo .FilterMap (commits , func (commit * models.Commit , _ int ) (string , bool ) {
279
+ commitHashes := lo .FilterMap (todoCommits , func (commit * models.Commit , _ int ) (string , bool ) {
253
280
return commit .Hash , commit .Hash != ""
254
281
})
255
282
@@ -273,7 +300,7 @@ func (self *CommitLoader) getHydratedRebasingCommits() ([]*models.Commit, error)
273
300
return nil , err
274
301
}
275
302
276
- findFullCommit := lo .Ternary (self . version . IsOlderThan ( 2 , 25 , 2 ) ,
303
+ findFullCommit := lo .Ternary (todoFileHasShortHashes ,
277
304
func (hash string ) * models.Commit {
278
305
for s , c := range fullCommits {
279
306
if strings .HasPrefix (s , hash ) {
@@ -286,8 +313,8 @@ func (self *CommitLoader) getHydratedRebasingCommits() ([]*models.Commit, error)
286
313
return fullCommits [hash ]
287
314
})
288
315
289
- hydratedCommits := make ([]* models.Commit , 0 , len (commits ))
290
- for _ , rebasingCommit := range commits {
316
+ hydratedCommits := make ([]* models.Commit , 0 , len (todoCommits ))
317
+ for _ , rebasingCommit := range todoCommits {
291
318
if rebasingCommit .Hash == "" {
292
319
hydratedCommits = append (hydratedCommits , rebasingCommit )
293
320
} else if commit := findFullCommit (rebasingCommit .Hash ); commit != nil {
@@ -304,7 +331,7 @@ func (self *CommitLoader) getHydratedRebasingCommits() ([]*models.Commit, error)
304
331
// git-rebase-todo example:
305
332
// pick ac446ae94ee560bdb8d1d057278657b251aaef17 ac446ae
306
333
// pick afb893148791a2fbd8091aeb81deba4930c73031 afb8931
307
- func (self * CommitLoader ) getRebasingCommits () []* models.Commit {
334
+ func (self * CommitLoader ) getRebasingCommits (addConflictingCommit bool ) []* models.Commit {
308
335
bytesContent , err := self .readFile (filepath .Join (self .repoPaths .WorktreeGitDirPath (), "rebase-merge/git-rebase-todo" ))
309
336
if err != nil {
310
337
self .Log .Error (fmt .Sprintf ("error occurred reading git-rebase-todo: %s" , err .Error ()))
@@ -322,13 +349,10 @@ func (self *CommitLoader) getRebasingCommits() []*models.Commit {
322
349
323
350
// See if the current commit couldn't be applied because it conflicted; if
324
351
// so, add a fake entry for it
325
- if conflictedCommitHash := self .getConflictedCommit (todos ); conflictedCommitHash != "" {
326
- commits = append (commits , & models.Commit {
327
- Hash : conflictedCommitHash ,
328
- Name : "" ,
329
- Status : models .StatusRebasing ,
330
- Action : models .ActionConflict ,
331
- })
352
+ if addConflictingCommit {
353
+ if conflictedCommit := self .getConflictedCommit (todos ); conflictedCommit != nil {
354
+ commits = append (commits , conflictedCommit )
355
+ }
332
356
}
333
357
334
358
for _ , t := range todos {
@@ -351,36 +375,34 @@ func (self *CommitLoader) getRebasingCommits() []*models.Commit {
351
375
return commits
352
376
}
353
377
354
- func (self * CommitLoader ) getConflictedCommit (todos []todo.Todo ) string {
378
+ func (self * CommitLoader ) getConflictedCommit (todos []todo.Todo ) * models. Commit {
355
379
bytesContent , err := self .readFile (filepath .Join (self .repoPaths .WorktreeGitDirPath (), "rebase-merge/done" ))
356
380
if err != nil {
357
381
self .Log .Error (fmt .Sprintf ("error occurred reading rebase-merge/done: %s" , err .Error ()))
358
- return ""
382
+ return nil
359
383
}
360
384
361
385
doneTodos , err := todo .Parse (bytes .NewBuffer (bytesContent ), self .config .GetCoreCommentChar ())
362
386
if err != nil {
363
387
self .Log .Error (fmt .Sprintf ("error occurred while parsing rebase-merge/done file: %s" , err .Error ()))
364
- return ""
388
+ return nil
365
389
}
366
390
367
- amendFileExists := false
368
- if _ , err := os .Stat (filepath .Join (self .repoPaths .WorktreeGitDirPath (), "rebase-merge/amend" )); err == nil {
369
- amendFileExists = true
370
- }
391
+ amendFileExists , _ := self .os .FileExists (filepath .Join (self .repoPaths .WorktreeGitDirPath (), "rebase-merge/amend" ))
392
+ messageFileExists , _ := self .os .FileExists (filepath .Join (self .repoPaths .WorktreeGitDirPath (), "rebase-merge/message" ))
371
393
372
- return self .getConflictedCommitImpl (todos , doneTodos , amendFileExists )
394
+ return self .getConflictedCommitImpl (todos , doneTodos , amendFileExists , messageFileExists )
373
395
}
374
396
375
- func (self * CommitLoader ) getConflictedCommitImpl (todos []todo.Todo , doneTodos []todo.Todo , amendFileExists bool ) string {
397
+ func (self * CommitLoader ) getConflictedCommitImpl (todos []todo.Todo , doneTodos []todo.Todo , amendFileExists bool , messageFileExists bool ) * models. Commit {
376
398
// Should never be possible, but just to be safe:
377
399
if len (doneTodos ) == 0 {
378
400
self .Log .Error ("no done entries in rebase-merge/done file" )
379
- return ""
401
+ return nil
380
402
}
381
403
lastTodo := doneTodos [len (doneTodos )- 1 ]
382
404
if lastTodo .Command == todo .Break || lastTodo .Command == todo .Exec || lastTodo .Command == todo .Reword {
383
- return ""
405
+ return nil
384
406
}
385
407
386
408
// In certain cases, git reschedules commands that failed. One example is if
@@ -391,7 +413,7 @@ func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos [
391
413
// same, the command was rescheduled.
392
414
if len (doneTodos ) > 0 && len (todos ) > 0 && doneTodos [len (doneTodos )- 1 ] == todos [0 ] {
393
415
// Command was rescheduled, no need to display it
394
- return ""
416
+ return nil
395
417
}
396
418
397
419
// Older versions of git have a bug whereby, if a command is rescheduled,
@@ -416,26 +438,99 @@ func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos [
416
438
if len (doneTodos ) >= 3 && len (todos ) > 0 && doneTodos [len (doneTodos )- 2 ] == todos [0 ] &&
417
439
doneTodos [len (doneTodos )- 1 ] == doneTodos [len (doneTodos )- 3 ] {
418
440
// Command was rescheduled, no need to display it
419
- return ""
441
+ return nil
420
442
}
421
443
422
444
if lastTodo .Command == todo .Edit {
423
445
if amendFileExists {
424
446
// Special case for "edit": if the "amend" file exists, the "edit"
425
447
// command was successful, otherwise it wasn't
426
- return ""
448
+ return nil
449
+ }
450
+
451
+ if ! messageFileExists {
452
+ // As an additional check, see if the "message" file exists; if it
453
+ // doesn't, it must be because a multi-commit cherry-pick or revert
454
+ // was performed in the meantime, which deleted both the amend file
455
+ // and the message file.
456
+ return nil
427
457
}
428
458
}
429
459
430
460
// I don't think this is ever possible, but again, just to be safe:
431
461
if lastTodo .Commit == "" {
432
462
self .Log .Error ("last command in rebase-merge/done file doesn't have a commit" )
433
- return ""
463
+ return nil
434
464
}
435
465
436
466
// Any other todo that has a commit associated with it must have failed with
437
467
// a conflict, otherwise we wouldn't have stopped the rebase:
438
- return lastTodo .Commit
468
+ return & models.Commit {
469
+ Hash : lastTodo .Commit ,
470
+ Action : lastTodo .Command ,
471
+ Status : models .StatusConflicted ,
472
+ }
473
+ }
474
+
475
+ func (self * CommitLoader ) getSequencerCommits () []* models.Commit {
476
+ bytesContent , err := self .readFile (filepath .Join (self .repoPaths .WorktreeGitDirPath (), "sequencer/todo" ))
477
+ if err != nil {
478
+ self .Log .Error (fmt .Sprintf ("error occurred reading sequencer/todo: %s" , err .Error ()))
479
+ // we assume an error means the file doesn't exist so we just return
480
+ return nil
481
+ }
482
+
483
+ commits := []* models.Commit {}
484
+
485
+ todos , err := todo .Parse (bytes .NewBuffer (bytesContent ), self .config .GetCoreCommentChar ())
486
+ if err != nil {
487
+ self .Log .Error (fmt .Sprintf ("error occurred while parsing sequencer/todo file: %s" , err .Error ()))
488
+ return nil
489
+ }
490
+
491
+ for _ , t := range todos {
492
+ if t .Commit == "" {
493
+ // Command does not have a commit associated, skip
494
+ continue
495
+ }
496
+ commits = utils .Prepend (commits , & models.Commit {
497
+ Hash : t .Commit ,
498
+ Name : t .Msg ,
499
+ Status : models .StatusRebasing ,
500
+ Action : t .Command ,
501
+ })
502
+ }
503
+
504
+ return commits
505
+ }
506
+
507
+ func (self * CommitLoader ) getConflictedSequencerCommit (workingTreeState models.WorkingTreeState ) * models.Commit {
508
+ var shaFile string
509
+ var action todo.TodoCommand
510
+ if workingTreeState .CherryPicking {
511
+ shaFile = "CHERRY_PICK_HEAD"
512
+ action = todo .Pick
513
+ } else if workingTreeState .Reverting {
514
+ shaFile = "REVERT_HEAD"
515
+ action = todo .Revert
516
+ } else {
517
+ return nil
518
+ }
519
+ bytesContent , err := self .readFile (filepath .Join (self .repoPaths .WorktreeGitDirPath (), shaFile ))
520
+ if err != nil {
521
+ self .Log .Error (fmt .Sprintf ("error occurred reading %s: %s" , shaFile , err .Error ()))
522
+ // we assume an error means the file doesn't exist so we just return
523
+ return nil
524
+ }
525
+ lines := strings .Split (string (bytesContent ), "\n " )
526
+ if len (lines ) == 0 {
527
+ return nil
528
+ }
529
+ return & models.Commit {
530
+ Hash : lines [0 ],
531
+ Status : models .StatusConflicted ,
532
+ Action : action ,
533
+ }
439
534
}
440
535
441
536
func setCommitMergedStatuses (ancestor string , commits []* models.Commit ) {
0 commit comments