project: make fast-forward merges explicit

Every caller expects _FastForward to reject a merge commit. Always pass
--ff-only and remove the optional parameter so a violated ancestry
assumption fails instead of silently creating a merge.

Bug: 553599402
Change-Id: I04690b5ca675cb7d3062df08d5e20cd82f5b9e82
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/634003
Tested-by: Gavin Mak <gavinmak@google.com>
Reviewed-by: Brian Gan <brgan@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
This commit is contained in:
Gavin Mak authored and gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com committed 2026-09-21 13:39:56 -07:00
1 parent 7bffc72b5b
commit 6321b26685
3 files changed
+30 -6

No files matched your search

+6 -5
View File
@@ -3963,14 +3963,15 @@ class Project:
if GitCommand(self, cmd).Wait() != 0:
raise GitError(f"{self.name} rebase {upstream} ", project=self.name)
def _FastForward(self, head, ffonly=False, quiet=True):
cmd = ["merge", "--no-stat", head]
if ffonly:
cmd.append("--ff-only")
def _FastForward(self, head: str, quiet: bool = True) -> None:
cmd = ["merge", "--no-stat", "--ff-only"]
if quiet:
cmd.append("-q")
cmd.append(head)
if GitCommand(self, cmd).Wait() != 0:
raise GitError(f"{self.name} merge {head} ", project=self.name)
raise GitError(
f"{self.name} merge --ff-only {head}", project=self.name
)
def _ReprojectCheckout(
self, revid: str, head: Optional[str], verbose: bool = False
+1 -1
View File
@@ -195,7 +195,7 @@ If no project is specified try to use current directory as a project.
elif opt.revert:
project._Revert(dl.commit)
elif opt.ffonly:
project._FastForward(dl.commit, ffonly=True)
project._FastForward(dl.commit)
else:
if opt.branch:
project.StartBranch(opt.branch, revision=dl.commit)
+23
View File
@@ -775,6 +775,29 @@ class ProjectTests(unittest.TestCase):
)
self.assertEqual(2, proj.work_git.DiffZ.call_count)
def test_fast_forward_always_rejects_merge_commits(self) -> None:
"""The fast-forward helper always passes --ff-only to Git."""
with utils_for_test.TempGitTree() as tempdir:
proj = _create_mock_project(tempdir)
with mock.patch.object(
project, "GitCommand", autospec=True
) as run_git:
run_git.return_value.Wait.return_value = 0
proj._FastForward("revision")
run_git.assert_called_once_with(
proj, ["merge", "--no-stat", "--ff-only", "-q", "revision"]
)
run_git.reset_mock()
proj._FastForward("revision", quiet=False)
run_git.assert_called_once_with(
proj, ["merge", "--no-stat", "--ff-only", "revision"]
)
run_git.return_value.Wait.return_value = 1
with self.assertRaises(error.GitError):
proj._FastForward("revision")
@unittest.skipUnless(
utils_for_test.supports_reftable(),
"git reftable support is required for this test",