Skip to content

Comments

Strict push/updateref with old commit id#36700

Draft
lunny wants to merge 2 commits intogo-gitea:mainfrom
lunny:lunny/strict_push_updateref_with_old_commit_id
Draft

Strict push/updateref with old commit id#36700
lunny wants to merge 2 commits intogo-gitea:mainfrom
lunny:lunny/strict_push_updateref_with_old_commit_id

Conversation

@lunny
Copy link
Member

@lunny lunny commented Feb 21, 2026

To prevent accidentally updating references or force-pushing over someone else’s work, include the previous commit ID as a lease (e.g., use --force-with-lease).


Generated by a coding agent with Codex 5.2 LLM.

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Feb 21, 2026
@github-actions github-actions bot added the modifies/go Pull requests that update Go code label Feb 21, 2026
@lunny lunny requested a review from Copilot February 22, 2026 05:50
Copy link
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 aims to make branch/ref updates safer by adding “old commit ID” protection (similar to --force-with-lease / conditional update-ref) to reduce the risk of overwriting concurrent changes during PR updates/merges and AGit ref updates.

Changes:

  • Switch PR merge and rebase-update pushes to use git.Push with ForceWithLease.
  • Add gitrepo.UpdateRefWithOld and plumb an optional oldCommitID through pull_service.UpdateRefWithOldCommit.
  • Add tests covering UpdateRefWithOld and PushOptions.ForceWithLease.

Reviewed changes

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

Show a summary per file
File Description
services/pull/update_rebase.go Uses --force-with-lease when pushing rebased head branch updates.
services/pull/pull.go Adds UpdateRefWithOldCommit and uses conditional ref update in gitrepo.
services/pull/merge.go Uses --force-with-lease for merge push to base branch.
services/agit/agit.go Uses UpdateRefWithOldCommit to protect AGit pull ref updates.
modules/gitrepo/ref.go Adds UpdateRefWithOld wrapper around git update-ref old-value argument.
modules/gitrepo/ref_test.go Adds unit test for UpdateRefWithOld.
modules/git/repo_push_test.go Adds unit test for PushOptions.ForceWithLease.

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

Comment on lines +417 to +419
oldCommitID, err := baseGitRepo.GetRefCommitID(git.BranchPrefix + pr.BaseBranch)
if err != nil {
return "", fmt.Errorf("failed to get base branch commit: %w", err)
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

The oldCommitID used for --force-with-lease is read from the base repo immediately before the push. That defeats the protection: if pr.BaseBranch advanced after the temp repo was fetched/merged, this lease will still match and the push can overwrite newer commits. The expected commit should be the base branch commit the merge was built on (e.g. the commit of original_base / mergeBaseSHA), so the push fails when the branch has moved during the merge.

Suggested change
oldCommitID, err := baseGitRepo.GetRefCommitID(git.BranchPrefix + pr.BaseBranch)
if err != nil {
return "", fmt.Errorf("failed to get base branch commit: %w", err)
// Use the base commit the merge was built on (merge base) for the lease, so that
// the push fails if the base branch has advanced since the merge was computed.
oldCommitID := pr.MergeBase
if oldCommitID == "" {
// Fallback: preserve previous behavior if no merge base is recorded.
oldCommitID, err = baseGitRepo.GetRefCommitID(git.BranchPrefix + pr.BaseBranch)
if err != nil {
return "", fmt.Errorf("failed to get base branch commit: %w", err)
}

Copilot uses AI. Check for mistakes.
Comment on lines 55 to 87
@@ -64,43 +76,21 @@ func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullReques
headUser = pr.HeadRepo.Owner
}

pushCmd := gitcmd.NewCommand("push", "-f", "head_repo").
AddDynamicArguments(tmpRepoStagingBranch + ":" + git.BranchPrefix + pr.HeadBranch)

// Push back to the head repository.
// TODO: this cause an api call to "/api/internal/hook/post-receive/...",
// that prevents us from doint the whole merge in one db transaction
mergeCtx.outbuf.Reset()

if err := pushCmd.
WithEnv(repo_module.FullPushingEnvironment(
return git.Push(ctx, mergeCtx.tmpBasePath, git.PushOptions{
Remote: "head_repo",
LocalRefName: tmpRepoStagingBranch,
Branch: git.BranchPrefix + pr.HeadBranch,
ForceWithLease: fmt.Sprintf("%s:%s", git.BranchPrefix+pr.HeadBranch, oldCommitID),
Env: repo_module.FullPushingEnvironment(
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

The lease commit is retrieved from pr.HeadRepo right before pushing, which only protects against changes in the narrow window between this read and the push. If the head branch moved after the temporary repo fetched tracking (i.e. during the rebase), this will still succeed and can overwrite someone else’s updates. Use the head/tracking commit ID that the rebase was based on (the fetched tracking ref) as the --force-with-lease expected value so the push fails if the branch changed during the operation.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +21
baseRepoPath := "../git/tests/repos/repo1_bare"
tempDir := t.TempDir()

require.NoError(t, git.Clone(ctx, baseRepoPath, tempDir, git.CloneRepoOptions{Bare: true}))

Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

This test clones into tempDir := t.TempDir(), but git clone fails if the destination directory already exists (even if empty). Use a non-existent subdirectory (e.g. filepath.Join(t.TempDir(), "repo.git")) or remove the directory before cloning, otherwise the test will be flaky/fail consistently.

Copilot uses AI. Check for mistakes.
@silverwind
Copy link
Member

Does this have a dependency or relation to #36653 or can it merge independently?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. modifies/go Pull requests that update Go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants