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