raw · 12071 bytes
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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | import subprocess import unittest from test_cli import TOML, Cli from test_claims import git from test_merge import IDENT class FinishTest(Cli): def setUp(self): super().setUp() git(self.root, "init", "-q", "-b", "master") (self.root / ".gitignore").write_text(".worktrees/\n.wf/\n") git(self.root, "add", "-A") git(self.root, "commit", "-qm", "init") self.wt = self.root / ".worktrees" / "sonnet" git(self.root, "worktree", "add", "-q", str(self.wt), "-b", "sonnet/t-three") def out(self, *args, cwd=None): return subprocess.run(["git", *args], cwd=cwd or self.root, capture_output=True, text=True).stdout def finish(self, *args, cwd=None): return self.wf("finish", *args, project=False, cwd=cwd or self.wt, env=IDENT) def write_code(self): (self.wt / "code.txt").write_text("x\n") def test_report_line_tool_commit(self): self.write_code() code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "code.txt", "--no-push", "--tool-commit", "abc1234") self.assertEqual(code, 0, err) self.assertRegex(out, r"report: commit [0-9a-f]+ tool abc1234\n$") def test_report_line_no_worktree(self): (self.root / "code.txt").write_text("x\n") code, out, err = self.wf("finish", "t-three", "-m", "ok", "--commit", "impl", "code.txt", "--no-push", project=False, cwd=self.root, env=IDENT) self.assertEqual(code, 0, err) sha = self.out("rev-parse", "--short", "HEAD").strip() self.assertTrue(out.endswith(f"report: commit {sha}\n"), out) def test_commit_done_merge_in_one(self): self.write_code() code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "code.txt", "--no-push") self.assertEqual((code, err), (0, ""), out) self.assertIn("done: t-three → tasks/archive.md\n", out) self.assertIn("committed code.txt\n", out) self.assertIn("merged sonnet/t-three into master\n", out) sha = self.out("log", "--format=%h", "-n1", "--grep=^impl$", "master").strip() self.assertTrue(out.endswith(f"report: commit {sha}\n"), out) self.assertNotIn("wf merge", out) # no merge-step reminder: finish merged self.assertEqual(self.out("log", "--format=%s", "master"), "t-three done\nimpl\ninit\n") self.assertEqual(self.out("status", "--porcelain"), "") self.assertEqual((self.root / "code.txt").read_text(), "x\n") self.assertIn("t-three", (self.root / "tasks" / "archive.md").read_text()) def test_wip_commits_notes_clears(self): self.write_code() self.wf("status", "t-three", "progress", "br", project=False, cwd=self.wt, env=IDENT) code, out, err = self.wf("wip", "t-three", "-m", "half done, next: tests", "--commit", "wip", "code.txt", project=False, cwd=self.wt, env=IDENT) self.assertEqual(code, 0, err) self.assertIn("committed code.txt", out) self.assertEqual(self.out("log", "--format=%s", "-n1", cwd=self.wt), "wip\n") self.assertEqual(self.out("status", "--porcelain", cwd=self.wt, ).count("code.txt"), 0) code, out, err = self.wf("show", "t-three", project=False, cwd=self.wt) self.assertIn("half done, next: tests", out) self.assertNotIn("in progress", out) def test_no_commit_bookkeeping_only(self): git(self.wt, "switch", "-q", "--detach", "master") code, out, err = self.finish("t-three", "-m", "ok", "--no-push") self.assertEqual((code, err), (0, ""), out) self.assertTrue(out.endswith("committed TASKS.md tasks/archive.md\n"), out) self.assertEqual(self.out("log", "--format=%s", "master"), "bookkeeping\ninit\n") def test_commit_tasks_only_no_path_lists_once(self): code, out, err = self.finish("t-three", "-m", "ok", "--commit", "bk", "--no-push") self.assertEqual((code, err), (0, ""), out) self.assertEqual(out.count("TASKS.md"), 1, out) self.assertIn("committed TASKS.md tasks/archive.md\n", out) self.assertEqual(self.out("log", "--format=%s", "master"), "t-three done\ninit\n") def test_commit_tasks_path_named_lists_once(self): code, out, err = self.finish("t-three", "-m", "ok", "--commit", "bk", "TASKS.md", "--no-push", cwd=self.root) self.assertEqual((code, err), (0, ""), out) self.assertEqual(out.count("TASKS.md"), 1, out) self.assertEqual(self.out("log", "--format=%s", "master"), "bk\ninit\n") def test_new_untracked_dir_file_path_accepted(self): (self.wt / "docs" / "notes").mkdir(parents=True) (self.wt / "docs" / "notes" / "SCOPE.md").write_text("s\n") code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "docs/notes/SCOPE.md", "--no-push") self.assertEqual((code, err), (0, ""), out) self.assertIn("docs/notes/SCOPE.md", self.out("show", "--stat", "--format=", "master~1")) def test_dirty_outside_paths_refused_before_done(self): self.write_code() (self.wt / "DESIGN.md").write_text("stray\n") code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "code.txt", "--no-push") self.assertEqual((code, err), (1, "wf: uncommitted changes outside the --commit paths: DESIGN.md\n")) self.assertIn("t-three", (self.root / "TASKS.md").read_text()) self.assertEqual(self.out("log", "--format=%s", "master"), "init\n") def test_gate_red_refused_before_done(self): (self.root / "workflow.toml").write_text(TOML + 'quick_gate = ["exit 3"]\n') git(self.root, "commit", "-qam", "gate") git(self.wt, "rebase", "-q", "master") code, out, err = self.finish("t-three", "-m", "ok", "--no-push") self.assertEqual(code, 1, out + err) self.assertIn("quick_gate 'exit 3' red", err) self.assertIn("wf add -p 0", err) self.assertIn("done+gate-red", err) self.assertIn("t-three", (self.root / "TASKS.md").read_text()) def test_paths_need_commit_message(self): code, out, err = self.finish("t-three", "-m", "ok", "code.txt") self.assertEqual(code, 2, out + err) self.assertIn("paths need --commit", err) def test_main_tree_commits_code_and_bookkeeping(self): (self.root / "code.txt").write_text("y\n") code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "code.txt", cwd=self.root) self.assertEqual((code, err), (0, ""), out) self.assertEqual(self.out("log", "--format=%s", "master"), "impl\ninit\n") self.assertEqual(self.out("show", "--stat", "--format=", "master").split("|")[0].strip(), "TASKS.md") self.assertEqual(self.out("status", "--porcelain"), "") def test_main_tree_pushes_home(self): bare = self.root.parent / "home-finish.git" subprocess.run(["git", "init", "-q", "--bare", str(bare)], check=True) git(self.root, "remote", "add", "home", str(bare)) (self.root / "code.txt").write_text("y\n") code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "code.txt", cwd=self.root) self.assertEqual((code, err), (0, ""), out) self.assertIn("pushed home\n", out) self.assertEqual(self.out("log", "--format=%s", "master", cwd=bare), "impl\ninit\n") def test_main_tree_no_push_flag(self): bare = self.root.parent / "home-finish2.git" subprocess.run(["git", "init", "-q", "--bare", str(bare)], check=True) git(self.root, "remote", "add", "home", str(bare)) (self.root / "code.txt").write_text("y\n") code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "code.txt", "--no-push", cwd=self.root) self.assertEqual((code, err), (0, ""), out) self.assertNotIn("pushed home", out) # books stay with the task: no half-done finish, no main-tree done of a worktree's task def test_path_outside_repo_refused_before_done(self): other = self.root.parent / "other-repo-file.md" other.write_text("x\n") code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", str(other), "--no-push") self.assertEqual(code, 1, out + err) self.assertIn("is outside this repo", err) self.assertIn("t-three", (self.root / "TASKS.md").read_text()) self.assertEqual(self.out("status", "--porcelain"), "") def test_missing_path_refused_before_done(self): code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "shared/CLAUDE.md", "--no-push") self.assertEqual(code, 1, out + err) self.assertIn("does not exist", err) self.assertIn("t-three", (self.root / "TASKS.md").read_text()) def test_unchanged_paths_refused_before_done(self): code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "workflow.toml", "--no-push") self.assertEqual(code, 1, out + err) self.assertIn("nothing to commit", err) self.assertIn("t-three", (self.root / "TASKS.md").read_text()) def test_worktree_books_copy_refused(self): (self.wt / "TASKS.md").write_text((self.wt / "TASKS.md").read_text() + "\n") code, out, err = self.finish("t-three", "-m", "ok", "--commit", "bk", "TASKS.md", "--no-push") self.assertEqual(code, 1, out + err) self.assertIn("copy of the books", err) self.assertIn("t-three", (self.root / "TASKS.md").read_text()) def test_already_done_resumes_commit_and_merge(self): self.write_code() code, out, err = self.wf("done", "t-three", "-m", "ok", project=False, cwd=self.wt, env=IDENT) self.assertEqual(code, 0, err) code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "code.txt", "--no-push") self.assertEqual((code, err), (0, ""), out) self.assertIn("t-three already done (tasks/archive.md): resuming", out) self.assertEqual(self.out("log", "--format=%s", "master"), "t-three done\nimpl\ninit\n") self.assertEqual(self.out("status", "--porcelain"), "") self.assertEqual((self.root / "tasks" / "archive.md").read_text().count("**t-three**"), 1) def test_main_tree_done_of_worktree_task_refused(self): self.wf("status", "t-three", "progress", "sonnet/t-three", project=False, cwd=self.wt, env=IDENT) for cmd in (["done", "t-three", "-m", "ok"], ["finish", "t-three", "-m", "ok", "--no-push"]): code, out, err = self.wf(*cmd, project=False, cwd=self.root, env=IDENT) self.assertEqual(code, 1, out + err) self.assertIn("in progress in worktree", err) self.assertIn("branch sonnet/t-three", err) self.assertIn("t-three", (self.root / "TASKS.md").read_text()) # from the worktree it goes through, books committed on master by the merge self.write_code() code, out, err = self.finish("t-three", "-m", "ok", "--commit", "impl", "code.txt", "--no-push") self.assertEqual((code, err), (0, ""), out) self.assertEqual(self.out("log", "--format=%s", "master"), "t-three done\nimpl\ninit\n") def test_main_tree_done_after_status_clear(self): self.wf("status", "t-three", "progress", "sonnet/t-three", project=False, cwd=self.wt, env=IDENT) self.wf("status", "t-three", "clear", project=False, cwd=self.root, env=IDENT) code, out, err = self.wf("done", "t-three", "-m", "ok", project=False, cwd=self.root, env=IDENT) self.assertEqual(code, 0, out + err) def test_main_tree_done_branch_without_worktree_ok(self): self.wf("status", "t-three", "progress", "fast/t-three", project=False, cwd=self.root, env=IDENT) code, out, err = self.wf("done", "t-three", "-m", "ok", project=False, cwd=self.root, env=IDENT) self.assertEqual(code, 0, out + err) if __name__ == "__main__": unittest.main() |