Skip to content

Fix the wrong push commits in the pull request when force push#36914

Merged
lunny merged 55 commits into
go-gitea:mainfrom
lunny:lunny/fix_force_push
Apr 4, 2026
Merged

Fix the wrong push commits in the pull request when force push#36914
lunny merged 55 commits into
go-gitea:mainfrom
lunny:lunny/fix_force_push

Conversation

@lunny
Copy link
Copy Markdown
Member

@lunny lunny commented Mar 17, 2026

Fix #36905

The changes focus on force-push PR timeline handling and commit range calculation:

  • Reworked pull-request push comment creation to use a new gitrepo.GetCommitIDsBetweenReverse helper, with special handling for force pushes (merge-base based range, tolerate missing/invalid old commits, and keep force-push timeline entries).
  • Added Comment.GetPushActionContent to parse push comment payloads and used it to delete only non-force-push push comments during force pushes.
  • Removed the old Repository.CommitsBetweenNotBase helper from modules/git/repo_commit.go in favor of the new commit ID range helper.
  • Added tests for GetCommitIDsBetweenReverse (normal range, notRef filtering, fallback branch usage) and expanded pull comment tests to cover force-push edge cases.
image

@lunny lunny added type/bug backport/v1.25 This PR should be backported to Gitea 1.25 labels Mar 17, 2026
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Mar 17, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses #36905 by fixing how commit lists are computed for pull request timeline “push” entries after a force-push, ensuring the timeline reflects the full set of commits currently on the PR branch (not just the delta from the previous head).

Changes:

  • Update force-push handling in CreatePushPullComment to compute PR commit IDs via merge-base comparison against the base branch.
  • Add a new helper (GetCompareCommitIDsWithMergeBase) in services/git plus unit test coverage for it.
  • Add a pull service test case intended to cover “missing old commit” scenarios during force-push.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
services/pull/comment.go Switch force-push commit enumeration to merge-base-based compare logic.
services/pull/comment_test.go Add a force-push test case targeting missing/unreachable old commit scenarios.
services/git/compare.go Introduce GetCompareCommitIDsWithMergeBase helper to list commits from merge-base..head.
services/git/compare_test.go Add unit test validating the helper’s output against CommitsBetweenNotBase.
services/git/main_test.go Add TestMain bootstrap for services/git tests (unittest DB init).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread services/pull/comment_test.go Outdated
Comment thread services/pull/comment.go Outdated
Comment thread services/git/compare.go Outdated
@wxiaoguang
Copy link
Copy Markdown
Contributor

Don't make it depends on "db"

Don't use func TestMain(m *testing.M) { unittest.MainTest(m) }

Why the simple git operation should depend on db?

@silverwind
Copy link
Copy Markdown
Member

silverwind commented Mar 17, 2026

In essence the fix should be pretty simple: The commit timeline (and tab count) should always show the actual commits present in the branch, ordered into the timeline with their authoring date. Force-push can rewrite the entire commit timeline, so must invalidate all commits in the timeline.

Could just make it completely git-based and skip storing in the DB entirely, but do whatever perform better, but a DB cache does bring the invalidation complexity.

silverwind

This comment was marked as outdated.

Copy link
Copy Markdown
Member

@silverwind silverwind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by Claude on behalf of @silverwind

Comment thread services/git/compare.go Outdated
Comment thread services/git/compare.go Outdated
lunny and others added 3 commits March 17, 2026 14:03
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com>
@lunny lunny requested a review from Copilot March 18, 2026 22:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses incorrect commit lists shown in pull request timeline “push” comments after a force-push by changing how commit IDs are collected for force-push events.

Changes:

  • Update force-push handling to compute commit IDs from the PR base branch to the new head, instead of oldCommitID..newCommitID.
  • Add a unit test case intended to cover force-push when the old commit is missing.
  • Introduce gitrepo.GetCommitIDsBetween() plus tests for it.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
services/pull/comment.go Changes force-push commit range calculation to a base-ref → new-head comparison.
services/pull/comment_test.go Adds a regression-oriented test for missing old commit in force-push.
modules/gitrepo/compare.go Adds a helper to return commit IDs for a rev-list range.
modules/gitrepo/compare_test.go Adds tests covering the new rev-list helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread services/pull/comment.go Outdated
Comment thread services/pull/comment.go Outdated
Comment thread services/pull/comment_test.go Outdated
Comment thread modules/gitrepo/compare.go Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes incorrect “pushed commits” timeline entries after force-pushes by changing how commit ranges are computed, adding a shared gitrepo helper, and extending tests around force-push behavior.

Changes:

  • Adjust force-push push-comment behavior to recalculate commit IDs differently and to delete only non-force-push push comments.
  • Add gitrepo.GetCommitIDsBetween (with tests) to centralize commit-range retrieval.
  • Add Comment.GetPushActionContent and expand pull push-comment tests for force-push scenarios.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
services/pull/comment.go Changes push-comment commit ID computation and force-push deletion logic.
services/pull/comment_test.go Adds/adjusts tests for force-push deletion and missing-old-commit scenarios.
modules/gitrepo/compare.go Introduces GetCommitIDsBetween helper based on git rev-list.
modules/gitrepo/compare_test.go Adds unit tests for GetCommitIDsBetween.
modules/git/repo_commit.go Removes CommitsBetweenNotBase implementation.
models/issues/comment.go Adds GetPushActionContent JSON decoder for PR push comment payloads.
Comments suppressed due to low confidence (1)

services/pull/comment_test.go:173

  • The head-vs-base-branch subtest validates comment counts, but it doesn't assert anything about the commit IDs captured in the non-force-push push comment created during a force-push. Given the bug being fixed is incorrect commit lists/counts after force-push, it would be good to fetch the non-force-push comment (where is_force_push is false) and assert that its commit_ids match the expected PR commits and do not include base-only commits.
		comment, err := CreatePushPullComment(t.Context(), pusher, pr, oldCommit.ID.String(), headCommit.ID.String(), true)
		assert.NoError(t, err)
		assert.NotNil(t, comment)
		var createdData issues_model.PushActionContent
		assert.NoError(t, json.Unmarshal([]byte(comment.Content), &createdData))
		assert.True(t, createdData.IsForcePush)

		comments, err = issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
			IssueID: pr.IssueID,
			Type:    issues_model.CommentTypePullRequestPush,
		})
		assert.NoError(t, err)
		// Two comments should exist now: one regular push comment and one force-push comment.
		assert.Len(t, comments, 2)


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread services/pull/comment.go Outdated
Comment thread services/pull/comment.go Outdated
Comment thread modules/gitrepo/compare.go Outdated
Comment thread modules/gitrepo/compare_test.go Outdated
Comment thread services/pull/comment.go Outdated
lunny and others added 3 commits March 21, 2026 09:29
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com>
@lunny lunny marked this pull request as ready for review March 30, 2026 23:12
@lunny
Copy link
Copy Markdown
Member Author

lunny commented Mar 30, 2026

  1. GetPushActionContent always returns nil error despite the signature — callers get a silent zero-value struct on malformed content; either propagate the unmarshal error or drop it from the signature entirely

We can do nothing if unmarshal failed.

  1. prepareOldCommitCommentsToDelete mutates newData.CommitIDs as a hidden side effect — the name only hints at returning IDs to delete, this will confuse future readers; rename or make the mutation explicit

Renamed at 628d769

  1. GetLastCommitIDsBetweenReverse — "Last" and "Reverse" are both confusing here; "Last" refers to the -n limit cap silently dropping old commits, "Reverse" is just the output ordering; GetCommitIDsBetween would be clearer

Renamed at 94345ee

  1. cleanUpOldCommitCommentsForNewForcePush logs errors internally and doesn't return them, so db.WithTx2 can't roll back on partial failures — the cleanup can leave comments in an inconsistent state if the content update succeeds but the delete fails

We don't need to roll back here.

  1. Passing the zero SHA (EmptyObjectID) as oldRef goes through gitRepo.GetRefCommitID which may not handle it gracefully — the force-push-with-missing-old-commit test case asserts require.NotNil(t, comment) but if GetRefCommitID errors on zero SHA the whole function returns early with created=false; this path needs an explicit guard

d3286f7

  1. The commit deduplication logic (stripping already-accounted-for commits from newData) has no test — a subtest where an old comment shares some but not all commits with the new push would cover the core prepareOldCommitCommentsToDelete logic
  2. Typo in GetLastCommitIDsBetweenReverse: "--not should be kep as the last parameter"kept; also the "future versions of git >= 2.28" comment is stale copy-paste, 2.28 shipped in 2020

Fixed.

Written by Claude Code

@silverwind
Copy link
Copy Markdown
Member

Review update (considering previous review)

What's been addressed

  1. --not baseBranch regression — Fixed. The non-force-push path now passes pr.BaseBranch as notRef, correctly excluding base branch commits from push timeline entries.

  2. CommitsBetweenNotBase removal — Addressed. The notRef parameter in GetCommitIDsBetweenReverse replicates the --not baseBranch semantics.

  3. "No merge base" fallback unbounded output — Partially addressed. The limit parameter caps output at 1000. The fallback semantics (listing commits reachable from EITHER ref) are still wrong in principle, but the limit prevents catastrophic behavior.

  4. genCmd mutation bug — Fixed. genCmd now creates a new command each call instead of mutating a shared one.

What's still outstanding

The reconciliation approach is still overly complex. reconcileOldCommitCommentsForForcePush modifies old comments in-place (removing commit IDs, deleting empty ones, deduplicating against the new commit set). GitHub simply preserves old push comments unchanged on force-push — old commit links may 404, which is acceptable. The current approach:

  • Mutates newData.CommitIDs as a side effect, making data flow hard to trace
  • Updates individual old comments inside the transaction, with errors only logged (not returned)
  • Has more surface area for edge-case bugs (malformed JSON, partial failures)

Tests still don't cover the reconciliation logic. All test cases create old comments with empty PushActionContent{} (no commit IDs). The reconciliation code's interesting paths — where old comments have real commit IDs that partially overlap with the new force-push commits — are never exercised.

New concerns

GetPushActionContent silently ignores unmarshal errors. _ = json.Unmarshal(...) returns an empty PushActionContent on malformed JSON (no error). During reconciliation, that empty content causes the comment to be deleted (no surviving commit IDs). This could silently destroy valid comments with corrupted content. Either propagate the error or don't return nil error when unmarshal fails.

cleanUpOldCommitCommentsForNewForcePush errors are swallowed inside the transaction. If cleanup partially fails, the transaction continues creating new comments on top of inconsistent state. This should either fail the transaction or the individual updates inside reconcileOldCommitCommentsForForcePush should also be resilient.

Multiple json.Marshal errors silently ignored. dataJSON, _ := json.Marshal(data) appears in 3 places.

@wxiaoguang
Copy link
Copy Markdown
Contributor

GetPushActionContent silently ignores unmarshal errors. _ = json.Unmarshal(...) returns an empty PushActionContent on malformed JSON (no error).

Tolerate corrupted database values, otherwise json.Unmarshal should never fail.

Multiple json.Marshal errors silently ignored. dataJSON, _ := json.Marshal(data) appears in 3 places.

json.Marshal never fails in these cases.

@wxiaoguang
Copy link
Copy Markdown
Contributor

wxiaoguang commented Apr 1, 2026

cleanUpOldCommitCommentsForNewForcePush errors are swallowed inside the transaction.

It is also a right decision.


@silverwind Can you please show your thoughts, instead of just copying AI slop?

You should have your judgement, don't let AI slop waste everyone's time or mislead others.

#37040 (comment)

image

@wxiaoguang
Copy link
Copy Markdown
Contributor

https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy

Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.

I believe it also applies to the reviews & questions: if you are reviewing or questioning, the reviews and questions are from you, not an AI model.

@lunny lunny requested a review from silverwind April 2, 2026 02:06
@lunny lunny added this to the 1.26.0 milestone Apr 2, 2026
@silverwind
Copy link
Copy Markdown
Member

silverwind commented Apr 2, 2026

@lunny will you address #36914 (comment)?

@silverwind Can you please show your thoughts, instead of just copying AI slop?

My requirement is:

  • Exact representenation of commits on the branch (likely addressed)
  • One force-push message per force-push, no "reconcilation" and no mutation (not addressed)

@lunny
Copy link
Copy Markdown
Member Author

lunny commented Apr 3, 2026

@lunny will you address #36914 (comment)?

@silverwind Can you please show your thoughts, instead of just copying AI slop?

My requirement is:

  • Exact representenation of commits on the branch (likely addressed)
  • One force-push message per force-push, no "reconcilation" and no mutation (not addressed)

I don't understand your second requirement? I think the current behavior matched what Github did.

Copy link
Copy Markdown
Member

@silverwind silverwind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it is an improvement, but should be iterated upon later.

@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Apr 3, 2026
@silverwind
Copy link
Copy Markdown
Member

Don't have time or motivation to dig deeper now, so I trust it is correct.

@bircni
Copy link
Copy Markdown
Member

bircni commented Apr 3, 2026

I don't feel good with this
@wxiaoguang what it's your opinion?
@Zettat123 as there were more changes could you also have a look at it again?

@lunny
Copy link
Copy Markdown
Member Author

lunny commented Apr 3, 2026

I think we can merge this now. I could fix it if there is any bug.

@lunny lunny added the reviewed/wait-merge This pull request is part of the merge queue. It will be merged soon. label Apr 4, 2026
@lunny lunny merged commit f59d1d3 into go-gitea:main Apr 4, 2026
26 checks passed
@lunny lunny deleted the lunny/fix_force_push branch April 4, 2026 23:28
@GiteaBot GiteaBot removed the reviewed/wait-merge This pull request is part of the merge queue. It will be merged soon. label Apr 4, 2026
Copy link
Copy Markdown
Member

@bircni bircni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

zjjhot added a commit to zjjhot/gitea that referenced this pull request Apr 7, 2026
* main:
  Repair duration display for bad stopped timestamps (go-gitea#37121)
  Add terraform state registry (go-gitea#36710)
  Add placeholder content for empty content page (go-gitea#37114)
  Improve control char rendering and escape button styling (go-gitea#37094)
  Add gpg signing for merge rebase and update by rebase (go-gitea#36701)
  Move package settings to package instead of being tied to version (go-gitea#37026)
  Merge some standalone Vite entries into index.js (go-gitea#37085)
  Update Nix flake (go-gitea#37110)
  [skip ci] Updated translations via Crowdin
  Fix the wrong push commits in the pull request when force push (go-gitea#36914)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. type/bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect commit count in timeline after force-push

7 participants