just playing with tangled
1// Copyright 2024 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::path::Path;
16
17use test_case::test_case;
18
19use crate::common::TestEnvironment;
20
21fn get_log_output_with_branches(test_env: &TestEnvironment, cwd: &Path) -> String {
22 // Don't include commit IDs since they will be different depending on
23 // whether the test runs with `jj commit` or `jj describe` + `jj new`.
24 let template = r#""branches{" ++ local_branches ++ "} desc: " ++ description"#;
25 test_env.jj_cmd_success(cwd, &["log", "-T", template])
26}
27
28fn set_advance_branches(test_env: &TestEnvironment, enabled: bool) {
29 if enabled {
30 test_env.add_config(
31 r#"[experimental-advance-branches]
32 enabled-branches = ["glob:*"]
33 "#,
34 );
35 } else {
36 test_env.add_config(
37 r#"[experimental-advance-branches]
38 enabled-branches = []
39 "#,
40 );
41 }
42}
43
44// Runs a command in the specified test environment and workspace path that
45// describes the current commit with `commit_message` and creates a new commit
46// on top of it.
47type CommitFn = fn(env: &TestEnvironment, workspace_path: &Path, commit_message: &str);
48
49// Implements CommitFn using the `jj commit` command.
50fn commit_cmd(env: &TestEnvironment, workspace_path: &Path, commit_message: &str) {
51 env.jj_cmd_ok(workspace_path, &["commit", "-m", commit_message]);
52}
53
54// Implements CommitFn using the `jj describe` and `jj new`.
55fn describe_new_cmd(env: &TestEnvironment, workspace_path: &Path, commit_message: &str) {
56 env.jj_cmd_ok(workspace_path, &["describe", "-m", commit_message]);
57 env.jj_cmd_ok(workspace_path, &["new"]);
58}
59
60// Check that enabling and disabling advance-branches works as expected.
61#[test_case(commit_cmd ; "commit")]
62#[test_case(describe_new_cmd; "new")]
63fn test_advance_branches_enabled(make_commit: CommitFn) {
64 let test_env = TestEnvironment::default();
65 test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
66 let workspace_path = test_env.env_root().join("repo");
67
68 // First, test with advance-branches enabled. Start by creating a branch on the
69 // root commit.
70 set_advance_branches(&test_env, true);
71 test_env.jj_cmd_ok(
72 &workspace_path,
73 &["branch", "create", "-r", "@-", "test_branch"],
74 );
75
76 // Check the initial state of the repo.
77 insta::allow_duplicates! {
78 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
79 @ branches{} desc:
80 ◉ branches{test_branch} desc:
81 "###);
82 }
83
84 // Run jj commit, which will advance the branch pointing to @-.
85 make_commit(&test_env, &workspace_path, "first");
86 insta::allow_duplicates! {
87 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
88 @ branches{} desc:
89 ◉ branches{test_branch} desc: first
90 ◉ branches{} desc:
91 "###);
92 }
93
94 // Now disable advance branches and commit again. The branch shouldn't move.
95 set_advance_branches(&test_env, false);
96 make_commit(&test_env, &workspace_path, "second");
97 insta::allow_duplicates! {
98 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
99 @ branches{} desc:
100 ◉ branches{} desc: second
101 ◉ branches{test_branch} desc: first
102 ◉ branches{} desc:
103 "###);
104 }
105}
106
107// Check that only a branch pointing to @- advances. Branches pointing to @ are
108// not advanced.
109#[test_case(commit_cmd ; "commit")]
110#[test_case(describe_new_cmd; "new")]
111fn test_advance_branches_at_minus(make_commit: CommitFn) {
112 let test_env = TestEnvironment::default();
113 test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
114 let workspace_path = test_env.env_root().join("repo");
115
116 set_advance_branches(&test_env, true);
117 test_env.jj_cmd_ok(&workspace_path, &["branch", "create", "test_branch"]);
118
119 insta::allow_duplicates! {
120 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
121 @ branches{test_branch} desc:
122 ◉ branches{} desc:
123 "###);
124 }
125
126 make_commit(&test_env, &workspace_path, "first");
127 insta::allow_duplicates! {
128 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
129 @ branches{} desc:
130 ◉ branches{test_branch} desc: first
131 ◉ branches{} desc:
132 "###);
133 }
134
135 // Create a second branch pointing to @. On the next commit, only the first
136 // branch, which points to @-, will advance.
137 test_env.jj_cmd_ok(&workspace_path, &["branch", "create", "test_branch2"]);
138 make_commit(&test_env, &workspace_path, "second");
139 insta::allow_duplicates! {
140 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
141 @ branches{} desc:
142 ◉ branches{test_branch test_branch2} desc: second
143 ◉ branches{} desc: first
144 ◉ branches{} desc:
145 "###);
146 }
147}
148
149// Test that per-branch overrides invert the behavior of
150// experimental-advance-branches.enabled.
151#[test_case(commit_cmd ; "commit")]
152#[test_case(describe_new_cmd; "new")]
153fn test_advance_branches_overrides(make_commit: CommitFn) {
154 let test_env = TestEnvironment::default();
155 test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
156 let workspace_path = test_env.env_root().join("repo");
157
158 // advance-branches is disabled by default.
159 test_env.jj_cmd_ok(
160 &workspace_path,
161 &["branch", "create", "-r", "@-", "test_branch"],
162 );
163
164 // Check the initial state of the repo.
165 insta::allow_duplicates! {
166 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
167 @ branches{} desc:
168 ◉ branches{test_branch} desc:
169 "###);
170 }
171
172 // Commit will not advance the branch since advance-branches is disabled.
173 make_commit(&test_env, &workspace_path, "first");
174 insta::allow_duplicates! {
175 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
176 @ branches{} desc:
177 ◉ branches{} desc: first
178 ◉ branches{test_branch} desc:
179 "###);
180 }
181
182 // Now enable advance branches for "test_branch", move the branch, and commit
183 // again.
184 test_env.add_config(
185 r#"[experimental-advance-branches]
186 enabled-branches = ["test_branch"]
187 "#,
188 );
189 test_env.jj_cmd_ok(
190 &workspace_path,
191 &["branch", "set", "test_branch", "-r", "@-"],
192 );
193 insta::allow_duplicates! {
194 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
195 @ branches{} desc:
196 ◉ branches{test_branch} desc: first
197 ◉ branches{} desc:
198 "###);
199 }
200 make_commit(&test_env, &workspace_path, "second");
201 insta::allow_duplicates! {
202 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
203 @ branches{} desc:
204 ◉ branches{test_branch} desc: second
205 ◉ branches{} desc: first
206 ◉ branches{} desc:
207 "###);
208 }
209
210 // Now disable advance branches for "test_branch" and "second_branch", which
211 // we will use later. Disabling always takes precedence over enabling.
212 test_env.add_config(
213 r#"[experimental-advance-branches]
214 enabled-branches = ["test_branch", "second_branch"]
215 disabled-branches = ["test_branch"]
216 "#,
217 );
218 make_commit(&test_env, &workspace_path, "third");
219 insta::allow_duplicates! {
220 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
221 @ branches{} desc:
222 ◉ branches{} desc: third
223 ◉ branches{test_branch} desc: second
224 ◉ branches{} desc: first
225 ◉ branches{} desc:
226 "###);
227 }
228
229 // If we create a new branch at @- and move test_branch there as well. When
230 // we commit, only "second_branch" will advance since "test_branch" is disabled.
231 test_env.jj_cmd_ok(
232 &workspace_path,
233 &["branch", "create", "second_branch", "-r", "@-"],
234 );
235 test_env.jj_cmd_ok(
236 &workspace_path,
237 &["branch", "set", "test_branch", "-r", "@-"],
238 );
239 insta::allow_duplicates! {
240 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
241 @ branches{} desc:
242 ◉ branches{second_branch test_branch} desc: third
243 ◉ branches{} desc: second
244 ◉ branches{} desc: first
245 ◉ branches{} desc:
246 "###);
247 }
248 make_commit(&test_env, &workspace_path, "fourth");
249 insta::allow_duplicates! {
250 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
251 @ branches{} desc:
252 ◉ branches{second_branch} desc: fourth
253 ◉ branches{test_branch} desc: third
254 ◉ branches{} desc: second
255 ◉ branches{} desc: first
256 ◉ branches{} desc:
257 "###);
258 }
259}
260
261// If multiple eligible branches point to @-, all of them will be advanced.
262#[test_case(commit_cmd ; "commit")]
263#[test_case(describe_new_cmd; "new")]
264fn test_advance_branches_multiple_branches(make_commit: CommitFn) {
265 let test_env = TestEnvironment::default();
266 test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
267 let workspace_path = test_env.env_root().join("repo");
268
269 set_advance_branches(&test_env, true);
270 test_env.jj_cmd_ok(
271 &workspace_path,
272 &["branch", "create", "-r", "@-", "first_branch"],
273 );
274 test_env.jj_cmd_ok(
275 &workspace_path,
276 &["branch", "create", "-r", "@-", "second_branch"],
277 );
278
279 insta::allow_duplicates! {
280 // Check the initial state of the repo.
281 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
282 @ branches{} desc:
283 ◉ branches{first_branch second_branch} desc:
284 "###);
285 }
286
287 // Both branches are eligible and both will advance.
288 make_commit(&test_env, &workspace_path, "first");
289 insta::allow_duplicates! {
290 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
291 @ branches{} desc:
292 ◉ branches{first_branch second_branch} desc: first
293 ◉ branches{} desc:
294 "###);
295 }
296}
297
298// Call `jj new` on an interior commit and see that the branch pointing to its
299// parent's parent is advanced.
300#[test]
301fn test_new_advance_branches_interior() {
302 let test_env = TestEnvironment::default();
303 test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
304 let workspace_path = test_env.env_root().join("repo");
305
306 set_advance_branches(&test_env, true);
307
308 // Check the initial state of the repo.
309 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
310 @ branches{} desc:
311 ◉ branches{} desc:
312 "###);
313
314 // Create a gap in the commits for us to insert our new commit with --before.
315 test_env.jj_cmd_ok(&workspace_path, &["commit", "-m", "first"]);
316 test_env.jj_cmd_ok(&workspace_path, &["commit", "-m", "second"]);
317 test_env.jj_cmd_ok(&workspace_path, &["commit", "-m", "third"]);
318 test_env.jj_cmd_ok(
319 &workspace_path,
320 &["branch", "create", "-r", "@---", "test_branch"],
321 );
322 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
323 @ branches{} desc:
324 ◉ branches{} desc: third
325 ◉ branches{} desc: second
326 ◉ branches{test_branch} desc: first
327 ◉ branches{} desc:
328 "###);
329
330 test_env.jj_cmd_ok(&workspace_path, &["new", "-r", "@--"]);
331 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
332 @ branches{} desc:
333 │ ◉ branches{} desc: third
334 ├─╯
335 ◉ branches{test_branch} desc: second
336 ◉ branches{} desc: first
337 ◉ branches{} desc:
338 "###);
339}
340
341// If the `--before` flag is passed to `jj new`, branches are not advanced.
342#[test]
343fn test_new_advance_branches_before() {
344 let test_env = TestEnvironment::default();
345 test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
346 let workspace_path = test_env.env_root().join("repo");
347
348 set_advance_branches(&test_env, true);
349
350 // Check the initial state of the repo.
351 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
352 @ branches{} desc:
353 ◉ branches{} desc:
354 "###);
355
356 // Create a gap in the commits for us to insert our new commit with --before.
357 test_env.jj_cmd_ok(&workspace_path, &["commit", "-m", "first"]);
358 test_env.jj_cmd_ok(&workspace_path, &["commit", "-m", "second"]);
359 test_env.jj_cmd_ok(&workspace_path, &["commit", "-m", "third"]);
360 test_env.jj_cmd_ok(
361 &workspace_path,
362 &["branch", "create", "-r", "@---", "test_branch"],
363 );
364 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
365 @ branches{} desc:
366 ◉ branches{} desc: third
367 ◉ branches{} desc: second
368 ◉ branches{test_branch} desc: first
369 ◉ branches{} desc:
370 "###);
371
372 test_env.jj_cmd_ok(&workspace_path, &["new", "--before", "-r", "@-"]);
373 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
374 ◉ branches{} desc: third
375 @ branches{} desc:
376 ◉ branches{} desc: second
377 ◉ branches{test_branch} desc: first
378 ◉ branches{} desc:
379 "###);
380}
381
382// If the `--after` flag is passed to `jj new`, branches are not advanced.
383#[test]
384fn test_new_advance_branches_after() {
385 let test_env = TestEnvironment::default();
386 test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
387 let workspace_path = test_env.env_root().join("repo");
388
389 set_advance_branches(&test_env, true);
390 test_env.jj_cmd_ok(
391 &workspace_path,
392 &["branch", "create", "-r", "@-", "test_branch"],
393 );
394
395 // Check the initial state of the repo.
396 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
397 @ branches{} desc:
398 ◉ branches{test_branch} desc:
399 "###);
400
401 test_env.jj_cmd_ok(&workspace_path, &["describe", "-m", "first"]);
402 test_env.jj_cmd_ok(&workspace_path, &["new", "--after"]);
403 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
404 @ branches{} desc:
405 ◉ branches{} desc: first
406 ◉ branches{test_branch} desc:
407 "###);
408}
409
410#[test]
411fn test_new_advance_branches_merge_children() {
412 let test_env = TestEnvironment::default();
413 test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
414 let workspace_path = test_env.env_root().join("repo");
415
416 set_advance_branches(&test_env, true);
417 test_env.jj_cmd_ok(&workspace_path, &["desc", "-m", "0"]);
418 test_env.jj_cmd_ok(&workspace_path, &["new", "-m", "1"]);
419 test_env.jj_cmd_ok(&workspace_path, &["new", "description(0)", "-m", "2"]);
420 test_env.jj_cmd_ok(
421 &workspace_path,
422 &["branch", "create", "test_branch", "-r", "description(0)"],
423 );
424
425 // Check the initial state of the repo.
426 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
427 @ branches{} desc: 2
428 │ ◉ branches{} desc: 1
429 ├─╯
430 ◉ branches{test_branch} desc: 0
431 ◉ branches{} desc:
432 "###);
433
434 // The branch won't advance because `jj new` had multiple targets.
435 test_env.jj_cmd_ok(
436 &workspace_path,
437 &["new", "description(1)", "description(2)"],
438 );
439 insta::assert_snapshot!(get_log_output_with_branches(&test_env, &workspace_path), @r###"
440 @ branches{} desc:
441 ├─╮
442 │ ◉ branches{} desc: 2
443 ◉ │ branches{} desc: 1
444 ├─╯
445 ◉ branches{test_branch} desc: 0
446 ◉ branches{} desc:
447 "###);
448}