mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-10-16 02:17:19 +00:00
sync: roll back repo when stable is rewound
The documented way to roll back a bad release is to force push an older release to stable, and docs/release-process.md says clients will then automatically downgrade. _PostRepoFetch already supports switching to an older commit, but it only runs when `RepoProject.HasChanges` is true, and that used the `MetaProject` check of whether the remote has commits that HEAD lacks. After a rewind it doesn't, so clients that had already upgraded stayed on the bad release. Make `RepoProject.HasChanges` true whenever the remote revision differs from HEAD, in either direction. `ManifestProject` keeps the forward-only check so local manifest commits are still rebased rather than dropped. Bug: 571204396 Change-Id: I248507f434d2552ce11a9afabe9a6335cb24ddbd Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/640130 Reviewed-by: Gavin Mak <gavinmak@google.com> Tested-by: Brian Gan <brgan@google.com> Commit-Queue: Brian Gan <brgan@google.com>
This commit is contained in:
1 parent
340b587e94
commit
9293cda144
2 files changed
+74
-7
No files matched your search
+12
@@ -5507,6 +5507,18 @@ class RepoProject(MetaProject):
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def HasChanges(self) -> bool:
|
||||
"""Does the remote revision differ from what is checked out?
|
||||
|
||||
Unlike MetaProject, this is also true when the remote moved backwards:
|
||||
a release rollback force pushes an older release to stable, and users
|
||||
who already upgraded have to move back to it too.
|
||||
"""
|
||||
# Not passing all_refs makes GetRevisionId peel annotated tags (from
|
||||
# e.g. --repo-rev=v2.50) to the commit that HEAD points at.
|
||||
return self.GetRevisionId() != self.GetHeadRevisionId()
|
||||
|
||||
|
||||
class ManifestProject(MetaProject):
|
||||
"""The MetaProject for manifests."""
|
||||
|
||||
+62
-7
@@ -20,7 +20,7 @@ from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
|
||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
@@ -2061,6 +2061,58 @@ def test_metaproject_has_changes_bounds_revision_walk() -> None:
|
||||
meta._revlist.assert_called_once_with("-1", "^HEAD", "remote")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"project_cls,revision,expected",
|
||||
(
|
||||
(project.RepoProject, "refs/remotes/origin/current", False),
|
||||
(project.RepoProject, "refs/remotes/origin/newer", True),
|
||||
(project.RepoProject, "refs/remotes/origin/divergent", True),
|
||||
# A release rollback rewinds stable, and repo itself has to follow.
|
||||
(project.RepoProject, "refs/remotes/origin/older", True),
|
||||
# --repo-rev can pin repo to an annotated tag, which must be peeled.
|
||||
(project.RepoProject, "refs/tags/current-tag", False),
|
||||
# Other MetaProjects only update when the remote has new commits.
|
||||
(project.ManifestProject, "refs/remotes/origin/current", False),
|
||||
(project.ManifestProject, "refs/remotes/origin/newer", True),
|
||||
(project.ManifestProject, "refs/remotes/origin/divergent", True),
|
||||
(project.ManifestProject, "refs/remotes/origin/older", False),
|
||||
),
|
||||
ids=lambda v: v.__name__ if isinstance(v, type) else None,
|
||||
)
|
||||
def test_metaproject_has_changes(
|
||||
project_cls: Type[project.MetaProject], revision: str, expected: bool
|
||||
) -> None:
|
||||
"""HasChanges compares the tracked revision with the checked out one."""
|
||||
with utils_for_test.TempGitTree() as tempdir:
|
||||
|
||||
def git(*args: str) -> str:
|
||||
return subprocess.run(
|
||||
["git", "-C", tempdir, *args],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout.strip()
|
||||
|
||||
def commit(msg: str) -> str:
|
||||
git("commit", "-q", "--allow-empty", "-m", msg)
|
||||
return git("rev-parse", "HEAD")
|
||||
|
||||
# older -> current -> newer, plus divergent branching off older.
|
||||
revs = {name: commit(name) for name in ("older", "current", "newer")}
|
||||
git("checkout", "-q", "--detach", revs["older"])
|
||||
revs["divergent"] = commit("divergent")
|
||||
git("checkout", "-q", "main")
|
||||
git("reset", "-q", "--hard", revs["current"])
|
||||
for name, rev in revs.items():
|
||||
git("update-ref", f"refs/remotes/origin/{name}", rev)
|
||||
git("tag", "-a", "-m", "current-tag", "current-tag", revs["current"])
|
||||
|
||||
proj = _create_meta_project(tempdir, project_cls)
|
||||
proj.revisionExpr = revision
|
||||
|
||||
assert proj.HasChanges is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"state,expect_failure",
|
||||
[
|
||||
@@ -3225,8 +3277,11 @@ class GetEnvVarsTests(unittest.TestCase):
|
||||
self.assertEqual(env["REPO_LREV"], "")
|
||||
|
||||
|
||||
def _create_manifest_project(tempdir: str) -> project.ManifestProject:
|
||||
"""Return a ManifestProject for a new .repo/ under |tempdir|."""
|
||||
def _create_meta_project(
|
||||
tempdir: str,
|
||||
project_cls: Type[project.MetaProject] = project.ManifestProject,
|
||||
) -> project.MetaProject:
|
||||
"""Return a |project_cls| for a new .repo/ under |tempdir|."""
|
||||
repodir = os.path.join(tempdir, ".repo")
|
||||
manifest_dir = os.path.join(repodir, "manifests")
|
||||
manifest_file = os.path.join(repodir, manifest_xml.MANIFEST_FILE_NAME)
|
||||
@@ -3234,7 +3289,7 @@ def _create_manifest_project(tempdir: str) -> project.ManifestProject:
|
||||
os.mkdir(manifest_dir)
|
||||
manifest = manifest_xml.XmlManifest(repodir, manifest_file)
|
||||
|
||||
return project.ManifestProject(
|
||||
return project_cls(
|
||||
manifest, "test/manifest", os.path.join(tempdir, ".git"), tempdir
|
||||
)
|
||||
|
||||
@@ -3243,7 +3298,7 @@ class FetchCmdTests(unittest.TestCase):
|
||||
"""Tests for fetch_cmd feature."""
|
||||
|
||||
def setUpManifest(self, tempdir):
|
||||
return _create_manifest_project(tempdir)
|
||||
return _create_meta_project(tempdir)
|
||||
|
||||
def _get_project(self, tempdir):
|
||||
proj = _create_mock_project(
|
||||
@@ -3750,7 +3805,7 @@ class ReprojectCmdTests(unittest.TestCase):
|
||||
def test_metaproject_never_uses_the_command(self) -> None:
|
||||
"""Test .repo/manifests and .repo/repo are checked out by Git."""
|
||||
with utils_for_test.TempGitTree() as tempdir:
|
||||
fakeproj = _create_manifest_project(tempdir)
|
||||
fakeproj = _create_meta_project(tempdir)
|
||||
fakeproj.config.SetString("repo.reprojectcmd", "echo hi")
|
||||
fakeproj.config.SetBoolean("repo.uselocalgitdirs", True)
|
||||
self.assertFalse(fakeproj.UseReprojectCmd)
|
||||
@@ -3758,7 +3813,7 @@ class ReprojectCmdTests(unittest.TestCase):
|
||||
def test_sync_reproject_cmd_requires_use_local_gitdirs(self) -> None:
|
||||
"""Test that repo.reprojectcmd requires repo.uselocalgitdirs."""
|
||||
with utils_for_test.TempGitTree() as tempdir:
|
||||
fakeproj = _create_manifest_project(tempdir)
|
||||
fakeproj = _create_meta_project(tempdir)
|
||||
|
||||
class DummyManifest:
|
||||
is_submanifest = False
|
||||
|
||||
Reference in new issue
Block a user