summaryrefslogtreecommitdiff
path: root/app-admin/aerie/files/aerie-0.2.1-protect-ahead-branch.patch
blob: 967e08dd1dffe0bb22a2b6e5ccbf833f934c8f24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
diff --git a/src/aerie/sync.py b/src/aerie/sync.py
index 858bb58..d58a6b7 100644
--- a/src/aerie/sync.py
+++ b/src/aerie/sync.py
@@ -76,6 +76,26 @@ def ensure_clean_git_tree(path: Path) -> None:
         )
 
 
+def count_local_commits_to_drop(path: Path, target: str) -> int:
+    commits = run(
+        ["git", "-C", str(path), "rev-list", "--count", f"{target}..HEAD"],
+        capture=True,
+    )
+    return int(commits.stdout.strip() or "0")
+
+
+def ensure_no_local_commits_to_drop(path: Path, target: str) -> None:
+    count = count_local_commits_to_drop(path, target)
+    if count:
+        raise AerieError(
+            f"cannot update {path}; local HEAD has {count} commit(s) that would be "
+            f"discarded by reset to {target}\n"
+            f"Inspect with: git -C {path} log --oneline {target}..HEAD\n"
+            "Publish those commits, disable auto-sync for generated repos, or use "
+            "'aerie sync --force' to discard them."
+        )
+
+
 def discard_local_changes(path: Path) -> None:
     run(["git", "-C", str(path), "reset", "--hard"])
     run(["git", "-C", str(path), "clean", "-fd"])
@@ -95,8 +115,11 @@ def update_repo(repo: RepoConfig, release: str, *, force: bool = False) -> None:
     else:
         ensure_clean_git_tree(repo.location)
     run(["git", "-C", str(repo.location), "fetch", "--prune", "origin"])
-    run(["git", "-C", str(repo.location), "checkout", "-B", release, f"origin/{release}"])
-    run(["git", "-C", str(repo.location), "reset", "--hard", f"origin/{release}"])
+    target = f"origin/{release}"
+    if not force:
+        ensure_no_local_commits_to_drop(repo.location, target)
+    run(["git", "-C", str(repo.location), "checkout", "-B", release, target])
+    run(["git", "-C", str(repo.location), "reset", "--hard", target])
 
 
 def sync_repo(
diff --git a/tests/test_sync.py b/tests/test_sync.py
index ab85c07..d15a670 100644
--- a/tests/test_sync.py
+++ b/tests/test_sync.py
@@ -60,6 +60,40 @@ class SyncTests(unittest.TestCase):
 
         run.assert_called_once_with(["git", "-C", "/repo", "status", "--porcelain"], capture=True)
 
+    def test_update_repo_rejects_local_commits_without_force(self):
+        repo = repo_config(Path("/repo"))
+
+        with patch("aerie.sync.run") as run:
+            run.side_effect = [
+                subprocess.CompletedProcess(args=[], returncode=0, stdout=""),
+                subprocess.CompletedProcess(args=[], returncode=0, stdout=""),
+                subprocess.CompletedProcess(args=[], returncode=0, stdout="2\n"),
+            ]
+            with self.assertRaisesRegex(AerieError, "local HEAD has 2 commit"):
+                update_repo(repo, "develop")
+
+        self.assertEqual(
+            run.call_args_list,
+            [
+                call(
+                    ["git", "-C", "/repo", "status", "--porcelain"],
+                    capture=True,
+                ),
+                call(["git", "-C", "/repo", "fetch", "--prune", "origin"]),
+                call(
+                    [
+                        "git",
+                        "-C",
+                        "/repo",
+                        "rev-list",
+                        "--count",
+                        "origin/develop..HEAD",
+                    ],
+                    capture=True,
+                ),
+            ],
+        )
+
     def test_run_reports_captured_git_status_stderr(self):
         with patch("aerie.sync.subprocess.run") as subprocess_run:
             subprocess_run.side_effect = subprocess.CalledProcessError(