just playing with tangled
1// Copyright 2022 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;
16use testutils::git;
17
18use crate::common::CommandOutput;
19use crate::common::TestEnvironment;
20use crate::common::TestWorkDir;
21
22fn git_repo_dir_for_jj_repo(work_dir: &TestWorkDir<'_>) -> std::path::PathBuf {
23 work_dir
24 .root()
25 .join(".jj")
26 .join("repo")
27 .join("store")
28 .join("git")
29}
30
31fn set_up(test_env: &TestEnvironment) {
32 test_env.run_jj_in(".", ["git", "init", "origin"]).success();
33 let origin_dir = test_env.work_dir("origin");
34 let origin_git_repo_path = git_repo_dir_for_jj_repo(&origin_dir);
35
36 origin_dir
37 .run_jj(["describe", "-m=description 1"])
38 .success();
39 origin_dir
40 .run_jj(["bookmark", "create", "-r@", "bookmark1"])
41 .success();
42 origin_dir
43 .run_jj(["new", "root()", "-m=description 2"])
44 .success();
45 origin_dir
46 .run_jj(["bookmark", "create", "-r@", "bookmark2"])
47 .success();
48 origin_dir.run_jj(["git", "export"]).success();
49
50 test_env
51 .run_jj_in(
52 ".",
53 [
54 "git",
55 "clone",
56 "--config=git.auto-local-bookmark=true",
57 origin_git_repo_path.to_str().unwrap(),
58 "local",
59 ],
60 )
61 .success();
62}
63
64#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
65#[test_case(true; "spawn a git subprocess for remote calls")]
66fn test_git_push_nothing(subprocess: bool) {
67 let test_env = TestEnvironment::default();
68 set_up(&test_env);
69 let work_dir = test_env.work_dir("local");
70 if !subprocess {
71 test_env.add_config("git.subprocess = false");
72 }
73 // Show the setup. `insta` has trouble if this is done inside `set_up()`
74 insta::allow_duplicates! {
75 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
76 bookmark1: xtvrqkyv d13ecdbd (empty) description 1
77 @origin: xtvrqkyv d13ecdbd (empty) description 1
78 bookmark2: rlzusymt 8476341e (empty) description 2
79 @origin: rlzusymt 8476341e (empty) description 2
80 [EOF]
81 ");
82 }
83 // No bookmarks to push yet
84 let output = work_dir.run_jj(["git", "push", "--all"]);
85 insta::allow_duplicates! {
86 insta::assert_snapshot!(output, @r"
87 ------- stderr -------
88 Nothing changed.
89 [EOF]
90 ");
91 }
92}
93
94#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
95#[test_case(true; "spawn a git subprocess for remote calls")]
96fn test_git_push_current_bookmark(subprocess: bool) {
97 let test_env = TestEnvironment::default();
98 set_up(&test_env);
99 let work_dir = test_env.work_dir("local");
100 if !subprocess {
101 test_env.add_config("git.subprocess = false");
102 }
103 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
104 // Update some bookmarks. `bookmark1` is not a current bookmark, but
105 // `bookmark2` and `my-bookmark` are.
106 work_dir
107 .run_jj(["describe", "bookmark1", "-m", "modified bookmark1 commit"])
108 .success();
109 work_dir.run_jj(["new", "bookmark2"]).success();
110 work_dir
111 .run_jj(["bookmark", "set", "bookmark2", "-r@"])
112 .success();
113 work_dir
114 .run_jj(["bookmark", "create", "-r@", "my-bookmark"])
115 .success();
116 work_dir.run_jj(["describe", "-m", "foo"]).success();
117 // Check the setup
118 insta::allow_duplicates! {
119 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
120 bookmark1: xtvrqkyv 0f8dc656 (empty) modified bookmark1 commit
121 @origin (ahead by 1 commits, behind by 1 commits): xtvrqkyv hidden d13ecdbd (empty) description 1
122 bookmark2: yostqsxw bc7610b6 (empty) foo
123 @origin (behind by 1 commits): rlzusymt 8476341e (empty) description 2
124 my-bookmark: yostqsxw bc7610b6 (empty) foo
125 [EOF]
126 ");
127 }
128 // First dry-run. `bookmark1` should not get pushed.
129 let output = work_dir.run_jj(["git", "push", "--allow-new", "--dry-run"]);
130 insta::allow_duplicates! {
131 insta::assert_snapshot!(output, @r"
132 ------- stderr -------
133 Changes to push to origin:
134 Move forward bookmark bookmark2 from 8476341eb395 to bc7610b65a91
135 Add bookmark my-bookmark to bc7610b65a91
136 Dry-run requested, not pushing.
137 [EOF]
138 ");
139 }
140 let output = work_dir.run_jj(["git", "push", "--allow-new"]);
141 insta::allow_duplicates! {
142 insta::assert_snapshot!(output, @r"
143 ------- stderr -------
144 Changes to push to origin:
145 Move forward bookmark bookmark2 from 8476341eb395 to bc7610b65a91
146 Add bookmark my-bookmark to bc7610b65a91
147 [EOF]
148 ");
149 }
150 insta::allow_duplicates! {
151 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
152 bookmark1: xtvrqkyv 0f8dc656 (empty) modified bookmark1 commit
153 @origin (ahead by 1 commits, behind by 1 commits): xtvrqkyv hidden d13ecdbd (empty) description 1
154 bookmark2: yostqsxw bc7610b6 (empty) foo
155 @origin: yostqsxw bc7610b6 (empty) foo
156 my-bookmark: yostqsxw bc7610b6 (empty) foo
157 @origin: yostqsxw bc7610b6 (empty) foo
158 [EOF]
159 ");
160 }
161
162 // Try pushing backwards
163 work_dir
164 .run_jj([
165 "bookmark",
166 "set",
167 "bookmark2",
168 "-rbookmark2-",
169 "--allow-backwards",
170 ])
171 .success();
172 // This behavior is a strangeness of our definition of the default push revset.
173 // We could consider changing it.
174 let output = work_dir.run_jj(["git", "push"]);
175 insta::allow_duplicates! {
176 insta::assert_snapshot!(output, @r"
177 ------- stderr -------
178 Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@
179 Nothing changed.
180 [EOF]
181 ");
182 }
183 // We can move a bookmark backwards
184 let output = work_dir.run_jj(["git", "push", "-bbookmark2"]);
185 insta::allow_duplicates! {
186 insta::assert_snapshot!(output, @r"
187 ------- stderr -------
188 Changes to push to origin:
189 Move backward bookmark bookmark2 from bc7610b65a91 to 8476341eb395
190 [EOF]
191 ");
192 }
193}
194
195#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
196#[test_case(true; "spawn a git subprocess for remote calls")]
197fn test_git_push_parent_bookmark(subprocess: bool) {
198 let test_env = TestEnvironment::default();
199 set_up(&test_env);
200 let work_dir = test_env.work_dir("local");
201 if !subprocess {
202 test_env.add_config("git.subprocess = false");
203 }
204 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
205 work_dir.run_jj(["edit", "bookmark1"]).success();
206 work_dir
207 .run_jj(["describe", "-m", "modified bookmark1 commit"])
208 .success();
209 work_dir
210 .run_jj(["new", "-m", "non-empty description"])
211 .success();
212 work_dir.write_file("file", "file");
213 let output = work_dir.run_jj(["git", "push"]);
214 insta::allow_duplicates! {
215 insta::assert_snapshot!(output, @r"
216 ------- stderr -------
217 Changes to push to origin:
218 Move sideways bookmark bookmark1 from d13ecdbda2a2 to e612d524a5c6
219 [EOF]
220 ");
221 }
222}
223
224#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
225#[test_case(true; "spawn a git subprocess for remote calls")]
226fn test_git_push_no_matching_bookmark(subprocess: bool) {
227 let test_env = TestEnvironment::default();
228 set_up(&test_env);
229 let work_dir = test_env.work_dir("local");
230 if !subprocess {
231 test_env.add_config("git.subprocess = false");
232 }
233 work_dir.run_jj(["new"]).success();
234 let output = work_dir.run_jj(["git", "push"]);
235 insta::allow_duplicates! {
236 insta::assert_snapshot!(output, @r"
237 ------- stderr -------
238 Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@
239 Nothing changed.
240 [EOF]
241 ");
242 }
243}
244
245#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
246#[test_case(true; "spawn a git subprocess for remote calls")]
247fn test_git_push_matching_bookmark_unchanged(subprocess: bool) {
248 let test_env = TestEnvironment::default();
249 set_up(&test_env);
250 let work_dir = test_env.work_dir("local");
251 if !subprocess {
252 test_env.add_config("git.subprocess = false");
253 }
254 work_dir.run_jj(["new", "bookmark1"]).success();
255 let output = work_dir.run_jj(["git", "push"]);
256 insta::allow_duplicates! {
257 insta::assert_snapshot!(output, @r"
258 ------- stderr -------
259 Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@
260 Nothing changed.
261 [EOF]
262 ");
263 }
264}
265
266/// Test that `jj git push` without arguments pushes a bookmark to the specified
267/// remote even if it's already up to date on another remote
268/// (`remote_bookmarks(remote=<remote>)..@` vs. `remote_bookmarks()..@`).
269#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
270#[test_case(true; "spawn a git subprocess for remote calls")]
271fn test_git_push_other_remote_has_bookmark(subprocess: bool) {
272 let test_env = TestEnvironment::default();
273 set_up(&test_env);
274 let work_dir = test_env.work_dir("local");
275 if !subprocess {
276 test_env.add_config("git.subprocess = false");
277 }
278 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
279 // Create another remote (but actually the same)
280 let other_remote_path = test_env
281 .env_root()
282 .join("origin")
283 .join(".jj")
284 .join("repo")
285 .join("store")
286 .join("git");
287 work_dir
288 .run_jj([
289 "git",
290 "remote",
291 "add",
292 "other",
293 other_remote_path.to_str().unwrap(),
294 ])
295 .success();
296 // Modify bookmark1 and push it to `origin`
297 work_dir.run_jj(["edit", "bookmark1"]).success();
298 work_dir.run_jj(["describe", "-m=modified"]).success();
299 let output = work_dir.run_jj(["git", "push"]);
300 insta::allow_duplicates! {
301 insta::assert_snapshot!(output, @r"
302 ------- stderr -------
303 Changes to push to origin:
304 Move sideways bookmark bookmark1 from d13ecdbda2a2 to a657f1b61b94
305 [EOF]
306 ");
307 }
308 // Since it's already pushed to origin, nothing will happen if push again
309 let output = work_dir.run_jj(["git", "push"]);
310 insta::allow_duplicates! {
311 insta::assert_snapshot!(output, @r"
312 ------- stderr -------
313 Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@
314 Nothing changed.
315 [EOF]
316 ");
317 }
318 // The bookmark was moved on the "other" remote as well (since it's actually the
319 // same remote), but `jj` is not aware of that since it thinks this is a
320 // different remote. So, the push should fail.
321 //
322 // But it succeeds! That's because the bookmark is created at the same location
323 // as it is on the remote. This would also work for a descendant.
324 //
325 // TODO: Saner test?
326 let output = work_dir.run_jj(["git", "push", "--allow-new", "--remote=other"]);
327 insta::allow_duplicates! {
328 insta::assert_snapshot!(output, @r"
329 ------- stderr -------
330 Changes to push to other:
331 Add bookmark bookmark1 to a657f1b61b94
332 [EOF]
333 ");
334 }
335}
336
337#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
338#[test_case(true; "spawn a git subprocess for remote calls")]
339fn test_git_push_forward_unexpectedly_moved(subprocess: bool) {
340 let test_env = TestEnvironment::default();
341 set_up(&test_env);
342 let work_dir = test_env.work_dir("local");
343 if !subprocess {
344 test_env.add_config("git.subprocess = false");
345 }
346
347 // Move bookmark1 forward on the remote
348 let origin_dir = test_env.work_dir("origin");
349 origin_dir
350 .run_jj(["new", "bookmark1", "-m=remote"])
351 .success();
352 origin_dir.write_file("remote", "remote");
353 origin_dir
354 .run_jj(["bookmark", "set", "bookmark1", "-r@"])
355 .success();
356 origin_dir.run_jj(["git", "export"]).success();
357
358 // Move bookmark1 forward to another commit locally
359 work_dir.run_jj(["new", "bookmark1", "-m=local"]).success();
360 work_dir.write_file("local", "local");
361 work_dir
362 .run_jj(["bookmark", "set", "bookmark1", "-r@"])
363 .success();
364
365 // Pushing should fail
366 let output = work_dir.run_jj(["git", "push"]);
367 if subprocess {
368 insta::assert_snapshot!(output, @r"
369 ------- stderr -------
370 Changes to push to origin:
371 Move forward bookmark bookmark1 from d13ecdbda2a2 to 6750425ff51c
372 Error: Failed to push some bookmarks
373 Hint: The following references unexpectedly moved on the remote:
374 refs/heads/bookmark1 (reason: stale info)
375 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
376 [EOF]
377 [exit status: 1]
378 ");
379 } else {
380 insta::assert_snapshot!(output, @r"
381 ------- stderr -------
382 Changes to push to origin:
383 Move forward bookmark bookmark1 from d13ecdbda2a2 to 6750425ff51c
384 Error: Failed to push some bookmarks
385 Hint: The following references unexpectedly moved on the remote:
386 refs/heads/bookmark1
387 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
388 [EOF]
389 [exit status: 1]
390 ");
391 }
392}
393
394#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
395#[test_case(true; "spawn a git subprocess for remote calls")]
396fn test_git_push_sideways_unexpectedly_moved(subprocess: bool) {
397 let test_env = TestEnvironment::default();
398 set_up(&test_env);
399 let work_dir = test_env.work_dir("local");
400 if !subprocess {
401 test_env.add_config("git.subprocess = false");
402 }
403
404 // Move bookmark1 forward on the remote
405 let origin_dir = test_env.work_dir("origin");
406 origin_dir
407 .run_jj(["new", "bookmark1", "-m=remote"])
408 .success();
409 origin_dir.write_file("remote", "remote");
410 origin_dir
411 .run_jj(["bookmark", "set", "bookmark1", "-r@"])
412 .success();
413 insta::allow_duplicates! {
414 insta::assert_snapshot!(get_bookmark_output(&origin_dir), @r"
415 bookmark1: vruxwmqv 80284bec remote
416 @git (behind by 1 commits): qpvuntsm d13ecdbd (empty) description 1
417 bookmark2: zsuskuln 8476341e (empty) description 2
418 @git: zsuskuln 8476341e (empty) description 2
419 [EOF]
420 ");
421 }
422 origin_dir.run_jj(["git", "export"]).success();
423
424 // Move bookmark1 sideways to another commit locally
425 work_dir.run_jj(["new", "root()", "-m=local"]).success();
426 work_dir.write_file("local", "local");
427 work_dir
428 .run_jj(["bookmark", "set", "bookmark1", "--allow-backwards", "-r@"])
429 .success();
430 insta::allow_duplicates! {
431 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
432 bookmark1: kmkuslsw 0f8bf988 local
433 @origin (ahead by 1 commits, behind by 1 commits): xtvrqkyv d13ecdbd (empty) description 1
434 bookmark2: rlzusymt 8476341e (empty) description 2
435 @origin: rlzusymt 8476341e (empty) description 2
436 [EOF]
437 ");
438 }
439
440 let output = work_dir.run_jj(["git", "push"]);
441 if subprocess {
442 insta::assert_snapshot!(output, @r"
443 ------- stderr -------
444 Changes to push to origin:
445 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 0f8bf988588e
446 Error: Failed to push some bookmarks
447 Hint: The following references unexpectedly moved on the remote:
448 refs/heads/bookmark1 (reason: stale info)
449 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
450 [EOF]
451 [exit status: 1]
452 ");
453 } else {
454 insta::assert_snapshot!(output, @r"
455 ------- stderr -------
456 Changes to push to origin:
457 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 0f8bf988588e
458 Error: Failed to push some bookmarks
459 Hint: The following references unexpectedly moved on the remote:
460 refs/heads/bookmark1
461 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
462 [EOF]
463 [exit status: 1]
464 ");
465 }
466}
467
468// This tests whether the push checks that the remote bookmarks are in expected
469// positions.
470#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
471#[test_case(true; "spawn a git subprocess for remote calls")]
472fn test_git_push_deletion_unexpectedly_moved(subprocess: bool) {
473 let test_env = TestEnvironment::default();
474 set_up(&test_env);
475 let work_dir = test_env.work_dir("local");
476 if !subprocess {
477 test_env.add_config("git.subprocess = false");
478 }
479
480 // Move bookmark1 forward on the remote
481 let origin_dir = test_env.work_dir("origin");
482 origin_dir
483 .run_jj(["new", "bookmark1", "-m=remote"])
484 .success();
485 origin_dir.write_file("remote", "remote");
486 origin_dir
487 .run_jj(["bookmark", "set", "bookmark1", "-r@"])
488 .success();
489 insta::allow_duplicates! {
490 insta::assert_snapshot!(get_bookmark_output(&origin_dir), @r"
491 bookmark1: vruxwmqv 80284bec remote
492 @git (behind by 1 commits): qpvuntsm d13ecdbd (empty) description 1
493 bookmark2: zsuskuln 8476341e (empty) description 2
494 @git: zsuskuln 8476341e (empty) description 2
495 [EOF]
496 ");
497 }
498 origin_dir.run_jj(["git", "export"]).success();
499
500 // Delete bookmark1 locally
501 work_dir
502 .run_jj(["bookmark", "delete", "bookmark1"])
503 .success();
504 insta::allow_duplicates! {
505 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
506 bookmark1 (deleted)
507 @origin: xtvrqkyv d13ecdbd (empty) description 1
508 bookmark2: rlzusymt 8476341e (empty) description 2
509 @origin: rlzusymt 8476341e (empty) description 2
510 [EOF]
511 ");
512 }
513
514 let output = work_dir.run_jj(["git", "push", "--bookmark", "bookmark1"]);
515 if subprocess {
516 insta::assert_snapshot!(output, @r"
517 ------- stderr -------
518 Changes to push to origin:
519 Delete bookmark bookmark1 from d13ecdbda2a2
520 Error: Failed to push some bookmarks
521 Hint: The following references unexpectedly moved on the remote:
522 refs/heads/bookmark1 (reason: stale info)
523 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
524 [EOF]
525 [exit status: 1]
526 ");
527 } else {
528 insta::assert_snapshot!(output, @r"
529 ------- stderr -------
530 Changes to push to origin:
531 Delete bookmark bookmark1 from d13ecdbda2a2
532 Error: Failed to push some bookmarks
533 Hint: The following references unexpectedly moved on the remote:
534 refs/heads/bookmark1
535 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
536 [EOF]
537 [exit status: 1]
538 ");
539 }
540}
541
542#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
543#[test_case(true; "spawn a git subprocess for remote calls")]
544fn test_git_push_unexpectedly_deleted(subprocess: bool) {
545 let test_env = TestEnvironment::default();
546 set_up(&test_env);
547 let work_dir = test_env.work_dir("local");
548 if !subprocess {
549 test_env.add_config("git.subprocess = false");
550 }
551
552 // Delete bookmark1 forward on the remote
553 let origin_dir = test_env.work_dir("origin");
554 origin_dir
555 .run_jj(["bookmark", "delete", "bookmark1"])
556 .success();
557 insta::allow_duplicates! {
558 insta::assert_snapshot!(get_bookmark_output(&origin_dir), @r"
559 bookmark1 (deleted)
560 @git: qpvuntsm d13ecdbd (empty) description 1
561 bookmark2: zsuskuln 8476341e (empty) description 2
562 @git: zsuskuln 8476341e (empty) description 2
563 [EOF]
564 ");
565 }
566 origin_dir.run_jj(["git", "export"]).success();
567
568 // Move bookmark1 sideways to another commit locally
569 work_dir.run_jj(["new", "root()", "-m=local"]).success();
570 work_dir.write_file("local", "local");
571 work_dir
572 .run_jj(["bookmark", "set", "bookmark1", "--allow-backwards", "-r@"])
573 .success();
574 insta::allow_duplicates! {
575 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
576 bookmark1: kpqxywon 1ebe27ba local
577 @origin (ahead by 1 commits, behind by 1 commits): xtvrqkyv d13ecdbd (empty) description 1
578 bookmark2: rlzusymt 8476341e (empty) description 2
579 @origin: rlzusymt 8476341e (empty) description 2
580 [EOF]
581 ");
582 }
583
584 // Pushing a moved bookmark fails if deleted on remote
585 let output = work_dir.run_jj(["git", "push"]);
586 if subprocess {
587 insta::assert_snapshot!(output, @r"
588 ------- stderr -------
589 Changes to push to origin:
590 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 1ebe27ba04bf
591 Error: Failed to push some bookmarks
592 Hint: The following references unexpectedly moved on the remote:
593 refs/heads/bookmark1 (reason: stale info)
594 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
595 [EOF]
596 [exit status: 1]
597 ");
598 } else {
599 insta::assert_snapshot!(output, @r"
600 ------- stderr -------
601 Changes to push to origin:
602 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 1ebe27ba04bf
603 Error: Failed to push some bookmarks
604 Hint: The following references unexpectedly moved on the remote:
605 refs/heads/bookmark1
606 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
607 [EOF]
608 [exit status: 1]
609 ");
610 }
611
612 work_dir
613 .run_jj(["bookmark", "delete", "bookmark1"])
614 .success();
615 insta::allow_duplicates! {
616 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
617 bookmark1 (deleted)
618 @origin: xtvrqkyv d13ecdbd (empty) description 1
619 bookmark2: rlzusymt 8476341e (empty) description 2
620 @origin: rlzusymt 8476341e (empty) description 2
621 [EOF]
622 ");
623 }
624
625 if subprocess {
626 // git does not allow to push a deleted bookmark if we expect it to exist even
627 // though it was already deleted
628 let output = work_dir.run_jj(["git", "push", "-bbookmark1"]);
629 insta::assert_snapshot!(output, @r"
630 ------- stderr -------
631 Changes to push to origin:
632 Delete bookmark bookmark1 from d13ecdbda2a2
633 Error: Failed to push some bookmarks
634 Hint: The following references unexpectedly moved on the remote:
635 refs/heads/bookmark1 (reason: stale info)
636 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
637 [EOF]
638 [exit status: 1]
639 ");
640 } else {
641 // Pushing a *deleted* bookmark succeeds if deleted on remote, even if we expect
642 // bookmark1@origin to exist and point somewhere.
643 let output = work_dir.run_jj(["git", "push", "-bbookmark1"]);
644 insta::assert_snapshot!(output, @r"
645 ------- stderr -------
646 Changes to push to origin:
647 Delete bookmark bookmark1 from d13ecdbda2a2
648 [EOF]
649 ");
650 }
651}
652
653#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
654#[test_case(true; "spawn a git subprocess for remote calls")]
655fn test_git_push_creation_unexpectedly_already_exists(subprocess: bool) {
656 let test_env = TestEnvironment::default();
657 set_up(&test_env);
658 let work_dir = test_env.work_dir("local");
659 if !subprocess {
660 test_env.add_config("git.subprocess = false");
661 }
662
663 // Forget bookmark1 locally
664 work_dir
665 .run_jj(["bookmark", "forget", "--include-remotes", "bookmark1"])
666 .success();
667
668 // Create a new branh1
669 work_dir
670 .run_jj(["new", "root()", "-m=new bookmark1"])
671 .success();
672 work_dir.write_file("local", "local");
673 work_dir
674 .run_jj(["bookmark", "create", "-r@", "bookmark1"])
675 .success();
676 insta::allow_duplicates! {
677 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
678 bookmark1: yostqsxw cb17dcdc new bookmark1
679 bookmark2: rlzusymt 8476341e (empty) description 2
680 @origin: rlzusymt 8476341e (empty) description 2
681 [EOF]
682 ");
683 }
684
685 let output = work_dir.run_jj(["git", "push", "--allow-new"]);
686 if subprocess {
687 insta::assert_snapshot!(output, @r"
688 ------- stderr -------
689 Changes to push to origin:
690 Add bookmark bookmark1 to cb17dcdc74d5
691 Error: Failed to push some bookmarks
692 Hint: The following references unexpectedly moved on the remote:
693 refs/heads/bookmark1 (reason: stale info)
694 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
695 [EOF]
696 [exit status: 1]
697 ");
698 } else {
699 insta::assert_snapshot!(output, @r"
700 ------- stderr -------
701 Changes to push to origin:
702 Add bookmark bookmark1 to cb17dcdc74d5
703 Error: Failed to push some bookmarks
704 Hint: The following references unexpectedly moved on the remote:
705 refs/heads/bookmark1
706 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
707 [EOF]
708 [exit status: 1]
709 ");
710 }
711}
712
713#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
714#[test_case(true; "spawn a git subprocess for remote calls")]
715fn test_git_push_locally_created_and_rewritten(subprocess: bool) {
716 let test_env = TestEnvironment::default();
717 set_up(&test_env);
718 let work_dir = test_env.work_dir("local");
719 if !subprocess {
720 test_env.add_config("git.subprocess = false");
721 }
722 // Ensure that remote bookmarks aren't tracked automatically
723 test_env.add_config("git.auto-local-bookmark = false");
724
725 // Push locally-created bookmark
726 work_dir.run_jj(["new", "root()", "-mlocal 1"]).success();
727 work_dir
728 .run_jj(["bookmark", "create", "-r@", "my"])
729 .success();
730 let output = work_dir.run_jj(["git", "push"]);
731 insta::allow_duplicates! {
732 insta::assert_snapshot!(output, @r"
733 ------- stderr -------
734 Warning: Refusing to create new remote bookmark my@origin
735 Hint: Use --allow-new to push new bookmark. Use --remote to specify the remote to push to.
736 Nothing changed.
737 [EOF]
738 ");
739 }
740 // Either --allow-new or git.push-new-bookmarks=true should work
741 let output = work_dir.run_jj(["git", "push", "--allow-new", "--dry-run"]);
742 insta::allow_duplicates! {
743 insta::assert_snapshot!(output, @r"
744 ------- stderr -------
745 Changes to push to origin:
746 Add bookmark my to fcc999921ce9
747 Dry-run requested, not pushing.
748 [EOF]
749 ");
750 }
751 let output = work_dir.run_jj(["git", "push", "--config=git.push-new-bookmarks=true"]);
752 insta::allow_duplicates! {
753 insta::assert_snapshot!(output, @r"
754 ------- stderr -------
755 Changes to push to origin:
756 Add bookmark my to fcc999921ce9
757 [EOF]
758 ");
759 }
760
761 // Rewrite it and push again, which would fail if the pushed bookmark weren't
762 // set to "tracking"
763 work_dir.run_jj(["describe", "-mlocal 2"]).success();
764 insta::allow_duplicates! {
765 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
766 bookmark1: xtvrqkyv d13ecdbd (empty) description 1
767 @origin: xtvrqkyv d13ecdbd (empty) description 1
768 bookmark2: rlzusymt 8476341e (empty) description 2
769 @origin: rlzusymt 8476341e (empty) description 2
770 my: vruxwmqv 423bb660 (empty) local 2
771 @origin (ahead by 1 commits, behind by 1 commits): vruxwmqv hidden fcc99992 (empty) local 1
772 [EOF]
773 ");
774 }
775 let output = work_dir.run_jj(["git", "push"]);
776 insta::allow_duplicates! {
777 insta::assert_snapshot!(output, @r"
778 ------- stderr -------
779 Changes to push to origin:
780 Move sideways bookmark my from fcc999921ce9 to 423bb66069e7
781 [EOF]
782 ");
783 }
784}
785
786#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
787#[test_case(true; "spawn a git subprocess for remote calls")]
788fn test_git_push_multiple(subprocess: bool) {
789 let test_env = TestEnvironment::default();
790 set_up(&test_env);
791 let work_dir = test_env.work_dir("local");
792 if !subprocess {
793 test_env.add_config("git.subprocess = false");
794 }
795 work_dir
796 .run_jj(["bookmark", "delete", "bookmark1"])
797 .success();
798 work_dir
799 .run_jj(["bookmark", "set", "--allow-backwards", "bookmark2", "-r@"])
800 .success();
801 work_dir
802 .run_jj(["bookmark", "create", "-r@", "my-bookmark"])
803 .success();
804 work_dir.run_jj(["describe", "-m", "foo"]).success();
805 // Check the setup
806 insta::allow_duplicates! {
807 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
808 bookmark1 (deleted)
809 @origin: xtvrqkyv d13ecdbd (empty) description 1
810 bookmark2: yqosqzyt c4a3c310 (empty) foo
811 @origin (ahead by 1 commits, behind by 1 commits): rlzusymt 8476341e (empty) description 2
812 my-bookmark: yqosqzyt c4a3c310 (empty) foo
813 [EOF]
814 ");
815 }
816 // First dry-run
817 let output = work_dir.run_jj(["git", "push", "--all", "--deleted", "--dry-run"]);
818 insta::allow_duplicates! {
819 insta::assert_snapshot!(output, @r"
820 ------- stderr -------
821 Changes to push to origin:
822 Delete bookmark bookmark1 from d13ecdbda2a2
823 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92
824 Add bookmark my-bookmark to c4a3c3105d92
825 Dry-run requested, not pushing.
826 [EOF]
827 ");
828 }
829 // Dry run requesting two specific bookmarks
830 let output = work_dir.run_jj([
831 "git",
832 "push",
833 "--allow-new",
834 "-b=bookmark1",
835 "-b=my-bookmark",
836 "--dry-run",
837 ]);
838 insta::allow_duplicates! {
839 insta::assert_snapshot!(output, @r"
840 ------- stderr -------
841 Changes to push to origin:
842 Delete bookmark bookmark1 from d13ecdbda2a2
843 Add bookmark my-bookmark to c4a3c3105d92
844 Dry-run requested, not pushing.
845 [EOF]
846 ");
847 }
848 // Dry run requesting two specific bookmarks twice
849 let output = work_dir.run_jj([
850 "git",
851 "push",
852 "--allow-new",
853 "-b=bookmark1",
854 "-b=my-bookmark",
855 "-b=bookmark1",
856 "-b=glob:my-*",
857 "--dry-run",
858 ]);
859 insta::allow_duplicates! {
860 insta::assert_snapshot!(output, @r"
861 ------- stderr -------
862 Changes to push to origin:
863 Delete bookmark bookmark1 from d13ecdbda2a2
864 Add bookmark my-bookmark to c4a3c3105d92
865 Dry-run requested, not pushing.
866 [EOF]
867 ");
868 }
869 // Dry run with glob pattern
870 let output = work_dir.run_jj(["git", "push", "-b=glob:bookmark?", "--dry-run"]);
871 insta::allow_duplicates! {
872 insta::assert_snapshot!(output, @r"
873 ------- stderr -------
874 Changes to push to origin:
875 Delete bookmark bookmark1 from d13ecdbda2a2
876 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92
877 Dry-run requested, not pushing.
878 [EOF]
879 ");
880 }
881
882 // Unmatched bookmark name is error
883 let output = work_dir.run_jj(["git", "push", "-b=foo"]);
884 insta::allow_duplicates! {
885 insta::assert_snapshot!(output, @r"
886 ------- stderr -------
887 Error: No such bookmark: foo
888 [EOF]
889 [exit status: 1]
890 ");
891 }
892 let output = work_dir.run_jj(["git", "push", "-b=foo", "-b=glob:?bookmark"]);
893 insta::allow_duplicates! {
894 insta::assert_snapshot!(output, @r"
895 ------- stderr -------
896 Error: No matching bookmarks for patterns: foo, ?bookmark
897 [EOF]
898 [exit status: 1]
899 ");
900 }
901
902 // --deleted is required to push deleted bookmarks even with --all
903 let output = work_dir.run_jj(["git", "push", "--all", "--dry-run"]);
904 insta::assert_snapshot!(output, @r"
905 ------- stderr -------
906 Warning: Refusing to push deleted bookmark bookmark1
907 Hint: Push deleted bookmarks with --deleted or forget the bookmark to suppress this warning.
908 Changes to push to origin:
909 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92
910 Add bookmark my-bookmark to c4a3c3105d92
911 Dry-run requested, not pushing.
912 [EOF]
913 ");
914 let output = work_dir.run_jj(["git", "push", "--all", "--deleted", "--dry-run"]);
915 insta::assert_snapshot!(output, @r"
916 ------- stderr -------
917 Changes to push to origin:
918 Delete bookmark bookmark1 from d13ecdbda2a2
919 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92
920 Add bookmark my-bookmark to c4a3c3105d92
921 Dry-run requested, not pushing.
922 [EOF]
923 ");
924
925 let output = work_dir.run_jj(["git", "push", "--all", "--deleted"]);
926 insta::allow_duplicates! {
927 insta::assert_snapshot!(output, @r"
928 ------- stderr -------
929 Changes to push to origin:
930 Delete bookmark bookmark1 from d13ecdbda2a2
931 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92
932 Add bookmark my-bookmark to c4a3c3105d92
933 [EOF]
934 ");
935 }
936 insta::allow_duplicates! {
937 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
938 bookmark2: yqosqzyt c4a3c310 (empty) foo
939 @origin: yqosqzyt c4a3c310 (empty) foo
940 my-bookmark: yqosqzyt c4a3c310 (empty) foo
941 @origin: yqosqzyt c4a3c310 (empty) foo
942 [EOF]
943 ");
944 }
945 let output = work_dir.run_jj(["log", "-rall()"]);
946 insta::allow_duplicates! {
947 insta::assert_snapshot!(output, @r"
948 @ yqosqzyt test.user@example.com 2001-02-03 08:05:17 bookmark2 my-bookmark c4a3c310
949 │ (empty) foo
950 │ ○ rlzusymt test.user@example.com 2001-02-03 08:05:10 8476341e
951 ├─╯ (empty) description 2
952 │ ○ xtvrqkyv test.user@example.com 2001-02-03 08:05:08 d13ecdbd
953 ├─╯ (empty) description 1
954 ◆ zzzzzzzz root() 00000000
955 [EOF]
956 ");
957 }
958}
959
960#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
961#[test_case(true; "spawn a git subprocess for remote calls")]
962fn test_git_push_changes(subprocess: bool) {
963 let test_env = TestEnvironment::default();
964 set_up(&test_env);
965 let work_dir = test_env.work_dir("local");
966 if !subprocess {
967 test_env.add_config("git.subprocess = false");
968 }
969 work_dir.run_jj(["describe", "-m", "foo"]).success();
970 work_dir.write_file("file", "contents");
971 work_dir.run_jj(["new", "-m", "bar"]).success();
972 work_dir.write_file("file", "modified");
973
974 let output = work_dir.run_jj(["git", "push", "--change", "@"]);
975 insta::allow_duplicates! {
976 insta::assert_snapshot!(output, @r"
977 ------- stderr -------
978 Creating bookmark push-yostqsxwqrlt for revision yostqsxwqrlt
979 Changes to push to origin:
980 Add bookmark push-yostqsxwqrlt to cf1a53a8800a
981 [EOF]
982 ");
983 }
984 // test pushing two changes at once
985 work_dir.write_file("file", "modified2");
986 let output = work_dir.run_jj(["git", "push", "-c=(@|@-)"]);
987 insta::allow_duplicates! {
988 insta::assert_snapshot!(output, @r"
989 ------- stderr -------
990 Error: Revset `(@|@-)` resolved to more than one revision
991 Hint: The revset `(@|@-)` resolved to these revisions:
992 yostqsxw 16c16966 push-yostqsxwqrlt* | bar
993 yqosqzyt a050abf4 foo
994 Hint: Prefix the expression with `all:` to allow any number of revisions (i.e. `all:(@|@-)`).
995 [EOF]
996 [exit status: 1]
997 ");
998 }
999 // test pushing two changes at once, part 2
1000 let output = work_dir.run_jj(["git", "push", "-c=all:(@|@-)"]);
1001 insta::allow_duplicates! {
1002 insta::assert_snapshot!(output, @r"
1003 ------- stderr -------
1004 Creating bookmark push-yqosqzytrlsw for revision yqosqzytrlsw
1005 Changes to push to origin:
1006 Move sideways bookmark push-yostqsxwqrlt from cf1a53a8800a to 16c169664e9f
1007 Add bookmark push-yqosqzytrlsw to a050abf4ff07
1008 [EOF]
1009 ");
1010 }
1011 // specifying the same change twice doesn't break things
1012 work_dir.write_file("file", "modified3");
1013 let output = work_dir.run_jj(["git", "push", "-c=all:(@|@)"]);
1014 insta::allow_duplicates! {
1015 insta::assert_snapshot!(output, @r"
1016 ------- stderr -------
1017 Changes to push to origin:
1018 Move sideways bookmark push-yostqsxwqrlt from 16c169664e9f to ef6313d50ac1
1019 [EOF]
1020 ");
1021 }
1022
1023 // specifying the same bookmark with --change/--bookmark doesn't break things
1024 work_dir.write_file("file", "modified4");
1025 let output = work_dir.run_jj(["git", "push", "-c=@", "-b=push-yostqsxwqrlt"]);
1026 insta::allow_duplicates! {
1027 insta::assert_snapshot!(output, @r"
1028 ------- stderr -------
1029 Changes to push to origin:
1030 Move sideways bookmark push-yostqsxwqrlt from ef6313d50ac1 to c1e65d3a64ce
1031 [EOF]
1032 ");
1033 }
1034
1035 // try again with --change that moves the bookmark forward
1036 work_dir.write_file("file", "modified5");
1037 work_dir
1038 .run_jj([
1039 "bookmark",
1040 "set",
1041 "-r=@-",
1042 "--allow-backwards",
1043 "push-yostqsxwqrlt",
1044 ])
1045 .success();
1046 let output = work_dir.run_jj(["status"]);
1047 insta::allow_duplicates! {
1048 insta::assert_snapshot!(output, @r"
1049 Working copy changes:
1050 M file
1051 Working copy (@) : yostqsxw 38cb417c bar
1052 Parent commit (@-): yqosqzyt a050abf4 push-yostqsxwqrlt* push-yqosqzytrlsw | foo
1053 [EOF]
1054 ");
1055 }
1056 let output = work_dir.run_jj(["git", "push", "-c=@", "-b=push-yostqsxwqrlt"]);
1057 insta::allow_duplicates! {
1058 insta::assert_snapshot!(output, @r"
1059 ------- stderr -------
1060 Changes to push to origin:
1061 Move sideways bookmark push-yostqsxwqrlt from c1e65d3a64ce to 38cb417ce3a6
1062 [EOF]
1063 ");
1064 }
1065 let output = work_dir.run_jj(["status"]);
1066 insta::allow_duplicates! {
1067 insta::assert_snapshot!(output, @r"
1068 Working copy changes:
1069 M file
1070 Working copy (@) : yostqsxw 38cb417c push-yostqsxwqrlt | bar
1071 Parent commit (@-): yqosqzyt a050abf4 push-yqosqzytrlsw | foo
1072 [EOF]
1073 ");
1074 }
1075
1076 // Test changing `git.push-bookmark-prefix`. It causes us to push again.
1077 let output = work_dir.run_jj([
1078 "git",
1079 "push",
1080 "--config=git.push-bookmark-prefix=test-",
1081 "--change=@",
1082 ]);
1083 insta::allow_duplicates! {
1084 insta::assert_snapshot!(output, @r"
1085 ------- stderr -------
1086 Creating bookmark test-yostqsxwqrlt for revision yostqsxwqrlt
1087 Changes to push to origin:
1088 Add bookmark test-yostqsxwqrlt to 38cb417ce3a6
1089 [EOF]
1090 ");
1091 }
1092}
1093
1094#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1095#[test_case(true; "spawn a git subprocess for remote calls")]
1096fn test_git_push_changes_with_name(subprocess: bool) {
1097 let test_env = TestEnvironment::default();
1098 set_up(&test_env);
1099 let work_dir = test_env.work_dir("local");
1100 if subprocess {
1101 test_env.add_config("git.subprocess = true");
1102 }
1103 work_dir.run_jj(["describe", "-m", "foo"]).success();
1104 work_dir.write_file("file", "contents");
1105 work_dir.run_jj(["new", "-m", "pushed"]).success();
1106 work_dir.write_file("file", "modified");
1107
1108 // Normal behavior.
1109 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
1110 insta::allow_duplicates! {
1111 insta::assert_snapshot!(output, @r"
1112 ------- stderr -------
1113 Changes to push to origin:
1114 Add bookmark b1 to 3e677c129c1d
1115 [EOF]
1116 ");
1117 }
1118 // Spaces before the = sign are treated like part of the bookmark name and such
1119 // bookmarks cannot be pushed.
1120 let output = work_dir.run_jj(["git", "push", "--named", "b1 = @"]);
1121 insta::allow_duplicates! {
1122 insta::assert_snapshot!(output, @r"
1123 ------- stderr -------
1124 Error: Could not parse 'b1 ' as a bookmark name
1125 Caused by:
1126 1: Failed to parse bookmark name: Syntax error
1127 2: --> 1:3
1128 |
1129 1 | b1
1130 | ^---
1131 |
1132 = expected <EOI>
1133 Hint: For example, `--named myfeature=@` is valid syntax
1134 [EOF]
1135 [exit status: 2]
1136 ");
1137 }
1138 // test pushing a change with an empty name
1139 let output = work_dir.run_jj(["git", "push", "--named", "=@"]);
1140 insta::allow_duplicates! {
1141 insta::assert_snapshot!(output, @r"
1142 ------- stderr -------
1143 Error: Argument '=@' must have the form NAME=REVISION, with both NAME and REVISION non-empty
1144 Hint: For example, `--named myfeature=@` is valid syntax
1145 [EOF]
1146 [exit status: 2]
1147 ");
1148 }
1149 // Unparsable name
1150 let output = work_dir.run_jj(["git", "push", "--named", ":!:=@"]);
1151 insta::allow_duplicates! {
1152 insta::assert_snapshot!(output, @r"
1153 ------- stderr -------
1154 Error: Could not parse ':!:' as a bookmark name
1155 Caused by:
1156 1: Failed to parse bookmark name: Syntax error
1157 2: --> 1:1
1158 |
1159 1 | :!:
1160 | ^---
1161 |
1162 = expected <identifier>, <string_literal>, or <raw_string_literal>
1163 Hint: For example, `--named myfeature=@` is valid syntax
1164 [EOF]
1165 [exit status: 2]
1166 ");
1167 }
1168 // test pushing a change with an empty revision
1169 let output = work_dir.run_jj(["git", "push", "--named", "b2="]);
1170 insta::allow_duplicates! {
1171 insta::assert_snapshot!(output, @r"
1172 ------- stderr -------
1173 Error: Argument 'b2=' must have the form NAME=REVISION, with both NAME and REVISION non-empty
1174 Hint: For example, `--named myfeature=@` is valid syntax
1175 [EOF]
1176 [exit status: 2]
1177 ");
1178 }
1179 // test pushing a change with no equals sign
1180 let output = work_dir.run_jj(["git", "push", "--named", "b2"]);
1181 insta::allow_duplicates! {
1182 insta::assert_snapshot!(output, @r"
1183 ------- stderr -------
1184 Error: Argument 'b2' must include '=' and have the form NAME=REVISION
1185 Hint: For example, `--named myfeature=@` is valid syntax
1186 [EOF]
1187 [exit status: 2]
1188 ");
1189 }
1190
1191 // test pushing the same change with the same name again
1192 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
1193 insta::allow_duplicates! {
1194 insta::assert_snapshot!(output, @r"
1195 ------- stderr -------
1196 Error: Bookmark already exists: b1
1197 Hint: Use 'jj bookmark move' to move it, and 'jj git push -b b1 [--allow-new]' to push it
1198 [EOF]
1199 [exit status: 1]
1200 ");
1201 }
1202 // test pushing two changes at once
1203 work_dir.write_file("file", "modified2");
1204 let output = work_dir.run_jj(["git", "push", "--named=b2=all:(@|@-)"]);
1205 insta::allow_duplicates! {
1206 insta::assert_snapshot!(output, @r"
1207 ------- stderr -------
1208 Error: Revset `all:(@|@-)` resolved to more than one revision
1209 Hint: The revset `all:(@|@-)` resolved to these revisions:
1210 yostqsxw 101e6730 b1* | pushed
1211 yqosqzyt a050abf4 foo
1212 [EOF]
1213 [exit status: 1]
1214 ");
1215 }
1216
1217 // specifying the same bookmark with --named/--bookmark
1218 work_dir.write_file("file", "modified4");
1219 let output = work_dir.run_jj(["git", "push", "--named=b2=@", "-b=b2"]);
1220 insta::allow_duplicates! {
1221 insta::assert_snapshot!(output, @r"
1222 ------- stderr -------
1223 Changes to push to origin:
1224 Add bookmark b2 to 477da21559d5
1225 [EOF]
1226 ");
1227 }
1228}
1229
1230#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1231#[test_case(true; "spawn a git subprocess for remote calls")]
1232fn test_git_push_changes_with_name_deleted_tracked(subprocess: bool) {
1233 let test_env = TestEnvironment::default();
1234 set_up(&test_env);
1235 // Unset immutable_heads so that untracking branches does not move the working
1236 // copy
1237 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
1238 let work_dir = test_env.work_dir("local");
1239 if subprocess {
1240 test_env.add_config("git.subprocess = true");
1241 }
1242 // Create a second empty remote `another_remote`
1243 test_env
1244 .run_jj_in(".", ["git", "init", "another_remote"])
1245 .success();
1246 let another_remote_git_repo_path =
1247 git_repo_dir_for_jj_repo(&test_env.work_dir("another_remote"));
1248 work_dir
1249 .run_jj([
1250 "git",
1251 "remote",
1252 "add",
1253 "another_remote",
1254 another_remote_git_repo_path.to_str().unwrap(),
1255 ])
1256 .success();
1257 work_dir.run_jj(["describe", "-m", "foo"]).success();
1258 work_dir.write_file("file", "contents");
1259 work_dir.run_jj(["new", "-m", "pushed"]).success();
1260 work_dir.write_file("file", "modified");
1261 // Normal push as part of the test setup
1262 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
1263 insta::allow_duplicates! {
1264 insta::assert_snapshot!(output, @r"
1265 ------- stderr -------
1266 Changes to push to origin:
1267 Add bookmark b1 to fd39fc9ddae4
1268 [EOF]
1269 ");
1270 }
1271 work_dir.run_jj(["bookmark", "delete", "b1"]).success();
1272
1273 // Test the setup
1274 let output = work_dir
1275 .run_jj(["bookmark", "list", "--all", "b1"])
1276 .success();
1277 insta::allow_duplicates! {
1278 insta::assert_snapshot!(output, @r"
1279 b1 (deleted)
1280 @origin: kpqxywon fd39fc9d pushed
1281 [EOF]
1282 ------- stderr -------
1283 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
1284 [EOF]
1285 ");
1286 }
1287
1288 // Can't push `b1` with --named to the same or another remote if it's deleted
1289 // locally and still tracked on `origin`
1290 let output = work_dir.run_jj(["git", "push", "--named", "b1=@", "--remote=another_remote"]);
1291 insta::allow_duplicates! {
1292 insta::assert_snapshot!(output, @r"
1293 ------- stderr -------
1294 Error: Tracked remote bookmarks exist for deleted bookmark: b1
1295 Hint: Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark untrack 'glob:b1@*'` to disassociate them.
1296 [EOF]
1297 [exit status: 1]
1298 ");
1299 }
1300 let output = work_dir.run_jj(["git", "push", "--named", "b1=@", "--remote=origin"]);
1301 insta::allow_duplicates! {
1302 insta::assert_snapshot!(output, @r"
1303 ------- stderr -------
1304 Error: Tracked remote bookmarks exist for deleted bookmark: b1
1305 Hint: Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark untrack 'glob:b1@*'` to disassociate them.
1306 [EOF]
1307 [exit status: 1]
1308 ");
1309 }
1310
1311 // OK to push to a different remote once the bookmark is no longer tracked on
1312 // `origin`
1313 work_dir
1314 .run_jj(["bookmark", "untrack", "b1@origin"])
1315 .success();
1316 let output = work_dir
1317 .run_jj(["bookmark", "list", "--all", "b1"])
1318 .success();
1319 insta::allow_duplicates! {
1320 insta::assert_snapshot!(output, @r"
1321 b1@origin: kpqxywon fd39fc9d pushed
1322 [EOF]
1323 ");
1324 }
1325 let output = work_dir.run_jj(["git", "push", "--named", "b1=@", "--remote=another_remote"]);
1326 insta::allow_duplicates! {
1327 insta::assert_snapshot!(output, @r"
1328 ------- stderr -------
1329 Changes to push to another_remote:
1330 Add bookmark b1 to fd39fc9ddae4
1331 [EOF]
1332 ");
1333 }
1334 let output = work_dir
1335 .run_jj(["bookmark", "list", "--all", "b1"])
1336 .success();
1337 insta::allow_duplicates! {
1338 insta::assert_snapshot!(output, @r"
1339 b1: kpqxywon fd39fc9d pushed
1340 @another_remote: kpqxywon fd39fc9d pushed
1341 b1@origin: kpqxywon fd39fc9d pushed
1342 [EOF]
1343 ");
1344 }
1345}
1346
1347#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1348#[test_case(true; "spawn a git subprocess for remote calls")]
1349fn test_git_push_changes_with_name_untracked_or_forgotten(subprocess: bool) {
1350 let test_env = TestEnvironment::default();
1351 set_up(&test_env);
1352 let work_dir = test_env.work_dir("local");
1353 if subprocess {
1354 test_env.add_config("git.subprocess = true");
1355 }
1356 // Unset immutable_heads so that untracking branches does not move the working
1357 // copy
1358 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
1359 work_dir
1360 .run_jj(["describe", "-m", "pushed_to_remote"])
1361 .success();
1362 work_dir.write_file("file", "contents");
1363 work_dir
1364 .run_jj(["new", "-m", "child", "--no-edit"])
1365 .success();
1366 work_dir.write_file("file", "modified");
1367
1368 // Push a branch to a remote, but forget the local branch
1369 work_dir
1370 .run_jj(["git", "push", "--named", "b1=@"])
1371 .success();
1372 work_dir
1373 .run_jj(["bookmark", "untrack", "b1@origin"])
1374 .success();
1375 work_dir.run_jj(["bookmark", "delete", "b1"]).success();
1376
1377 let output = work_dir
1378 .run_jj(&[
1379 "log",
1380 "-r=::@+",
1381 r#"-T=separate(" ", commit_id.shortest(3), bookmarks, description)"#,
1382 ])
1383 .success();
1384 insta::allow_duplicates! {
1385 insta::assert_snapshot!(output, @r"
1386 ○ c9c child
1387 @ 10b b1@origin pushed_to_remote
1388 ◆ 000
1389 [EOF]
1390 ");
1391 }
1392 let output = work_dir
1393 .run_jj(["bookmark", "list", "--all", "b1"])
1394 .success();
1395 insta::allow_duplicates! {
1396 insta::assert_snapshot!(output, @r"
1397 b1@origin: yqosqzyt 10b6b209 pushed_to_remote
1398 [EOF]
1399 ");
1400 }
1401
1402 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
1403 insta::allow_duplicates! {
1404 insta::assert_snapshot!(output, @r"
1405 ------- stderr -------
1406 Error: Non-tracking remote bookmark b1@origin exists
1407 Hint: Run `jj bookmark track b1@origin` to import the remote bookmark.
1408 [EOF]
1409 [exit status: 1]
1410 ");
1411 }
1412
1413 let output = work_dir.run_jj(["git", "push", "--named", "b1=@+"]);
1414 insta::allow_duplicates! {
1415 insta::assert_snapshot!(output, @r"
1416 ------- stderr -------
1417 Error: Non-tracking remote bookmark b1@origin exists
1418 Hint: Run `jj bookmark track b1@origin` to import the remote bookmark.
1419 [EOF]
1420 [exit status: 1]
1421 ");
1422 }
1423
1424 // The bookmarked is still pushed to the remote, but let's entirely forget
1425 // it. In other words, let's forget the remote-tracking bookmarks.
1426 work_dir
1427 .run_jj(&["bookmark", "forget", "b1", "--include-remotes"])
1428 .success();
1429 let output = work_dir
1430 .run_jj(["bookmark", "list", "--all", "b1"])
1431 .success();
1432 insta::allow_duplicates! {
1433 insta::assert_snapshot!(output, @"");
1434 }
1435
1436 // Make sure push still errors if we try to push a bookmark with the same name
1437 // to a different location.
1438 let output = work_dir.run_jj(["git", "push", "--named", "b1=@+"]);
1439 insta::allow_duplicates! {
1440 insta::assert_snapshot!(output, @r"
1441 ------- stderr -------
1442 Changes to push to origin:
1443 Add bookmark b1 to c9c824c88955
1444 Error: Failed to push some bookmarks
1445 Hint: The following references unexpectedly moved on the remote:
1446 refs/heads/b1 (reason: stale info)
1447 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
1448 [EOF]
1449 [exit status: 1]
1450 ");
1451 }
1452
1453 // The bookmark is still forgotten
1454 let output = work_dir.run_jj(["bookmark", "list", "--all", "b1"]);
1455 insta::allow_duplicates! {
1456 insta::assert_snapshot!(output, @"");
1457 }
1458 // In this case, pushing the bookmark to the same location where it already is
1459 // succeeds. TODO: This seems pretty safe, but perhaps it should still show
1460 // an error or some sort of warning?
1461 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
1462 insta::allow_duplicates! {
1463 insta::assert_snapshot!(output, @r"
1464 ------- stderr -------
1465 Changes to push to origin:
1466 Add bookmark b1 to 10b6b209c4a3
1467 [EOF]
1468 ");
1469 }
1470}
1471
1472#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1473#[test_case(true; "spawn a git subprocess for remote calls")]
1474fn test_git_push_revisions(subprocess: bool) {
1475 let test_env = TestEnvironment::default();
1476 set_up(&test_env);
1477 let work_dir = test_env.work_dir("local");
1478 if !subprocess {
1479 test_env.add_config("git.subprocess = false");
1480 }
1481 work_dir.run_jj(["describe", "-m", "foo"]).success();
1482 work_dir.write_file("file", "contents");
1483 work_dir.run_jj(["new", "-m", "bar"]).success();
1484 work_dir
1485 .run_jj(["bookmark", "create", "-r@", "bookmark-1"])
1486 .success();
1487 work_dir.write_file("file", "modified");
1488 work_dir.run_jj(["new", "-m", "baz"]).success();
1489 work_dir
1490 .run_jj(["bookmark", "create", "-r@", "bookmark-2a"])
1491 .success();
1492 work_dir
1493 .run_jj(["bookmark", "create", "-r@", "bookmark-2b"])
1494 .success();
1495 work_dir.write_file("file", "modified again");
1496
1497 // Push an empty set
1498 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=none()"]);
1499 insta::allow_duplicates! {
1500 insta::assert_snapshot!(output, @r"
1501 ------- stderr -------
1502 Warning: No bookmarks point to the specified revisions: none()
1503 Nothing changed.
1504 [EOF]
1505 ");
1506 }
1507 // Push a revision with no bookmarks
1508 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@--"]);
1509 insta::allow_duplicates! {
1510 insta::assert_snapshot!(output, @r"
1511 ------- stderr -------
1512 Warning: No bookmarks point to the specified revisions: @--
1513 Nothing changed.
1514 [EOF]
1515 ");
1516 }
1517 // Push a revision with a single bookmark
1518 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@-", "--dry-run"]);
1519 insta::allow_duplicates! {
1520 insta::assert_snapshot!(output, @r"
1521 ------- stderr -------
1522 Changes to push to origin:
1523 Add bookmark bookmark-1 to 5f432a855e59
1524 Dry-run requested, not pushing.
1525 [EOF]
1526 ");
1527 }
1528 // Push multiple revisions of which some have bookmarks
1529 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@--", "-r=@-", "--dry-run"]);
1530 insta::allow_duplicates! {
1531 insta::assert_snapshot!(output, @r"
1532 ------- stderr -------
1533 Warning: No bookmarks point to the specified revisions: @--
1534 Changes to push to origin:
1535 Add bookmark bookmark-1 to 5f432a855e59
1536 Dry-run requested, not pushing.
1537 [EOF]
1538 ");
1539 }
1540 // Push a revision with a multiple bookmarks
1541 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@", "--dry-run"]);
1542 insta::allow_duplicates! {
1543 insta::assert_snapshot!(output, @r"
1544 ------- stderr -------
1545 Changes to push to origin:
1546 Add bookmark bookmark-2a to 84f499037f5c
1547 Add bookmark bookmark-2b to 84f499037f5c
1548 Dry-run requested, not pushing.
1549 [EOF]
1550 ");
1551 }
1552 // Repeating a commit doesn't result in repeated messages about the bookmark
1553 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@-", "-r=@-", "--dry-run"]);
1554 insta::allow_duplicates! {
1555 insta::assert_snapshot!(output, @r"
1556 ------- stderr -------
1557 Changes to push to origin:
1558 Add bookmark bookmark-1 to 5f432a855e59
1559 Dry-run requested, not pushing.
1560 [EOF]
1561 ");
1562 }
1563}
1564
1565#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1566#[test_case(true; "spawn a git subprocess for remote calls")]
1567fn test_git_push_mixed(subprocess: bool) {
1568 let test_env = TestEnvironment::default();
1569 set_up(&test_env);
1570 let work_dir = test_env.work_dir("local");
1571 if !subprocess {
1572 test_env.add_config("git.subprocess = false");
1573 }
1574 work_dir.run_jj(["describe", "-m", "foo"]).success();
1575 work_dir.write_file("file", "contents");
1576 work_dir.run_jj(["new", "-m", "bar"]).success();
1577 work_dir
1578 .run_jj(["bookmark", "create", "-r@", "bookmark-1"])
1579 .success();
1580 work_dir.write_file("file", "modified");
1581 work_dir.run_jj(["new", "-m", "baz"]).success();
1582 work_dir
1583 .run_jj(["bookmark", "create", "-r@", "bookmark-2a"])
1584 .success();
1585 work_dir
1586 .run_jj(["bookmark", "create", "-r@", "bookmark-2b"])
1587 .success();
1588 work_dir.write_file("file", "modified again");
1589
1590 // --allow-new is not implied for --bookmark=.. and -r=..
1591 let output = work_dir.run_jj([
1592 "git",
1593 "push",
1594 "--change=@--",
1595 "--bookmark=bookmark-1",
1596 "-r=@",
1597 ]);
1598 insta::allow_duplicates! {
1599 insta::assert_snapshot!(output, @r"
1600 ------- stderr -------
1601 Creating bookmark push-yqosqzytrlsw for revision yqosqzytrlsw
1602 Error: Refusing to create new remote bookmark bookmark-1@origin
1603 Hint: Use --allow-new to push new bookmark. Use --remote to specify the remote to push to.
1604 [EOF]
1605 [exit status: 1]
1606 ");
1607 }
1608
1609 let output = work_dir.run_jj([
1610 "git",
1611 "push",
1612 "--allow-new",
1613 "--change=@--",
1614 "--bookmark=bookmark-1",
1615 "-r=@",
1616 ]);
1617 insta::allow_duplicates! {
1618 insta::assert_snapshot!(output, @r"
1619 ------- stderr -------
1620 Creating bookmark push-yqosqzytrlsw for revision yqosqzytrlsw
1621 Changes to push to origin:
1622 Add bookmark push-yqosqzytrlsw to a050abf4ff07
1623 Add bookmark bookmark-1 to 5f432a855e59
1624 Add bookmark bookmark-2a to 84f499037f5c
1625 Add bookmark bookmark-2b to 84f499037f5c
1626 [EOF]
1627 ");
1628 }
1629}
1630
1631#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1632#[test_case(true; "spawn a git subprocess for remote calls")]
1633fn test_git_push_unsnapshotted_change(subprocess: bool) {
1634 let test_env = TestEnvironment::default();
1635 set_up(&test_env);
1636 let work_dir = test_env.work_dir("local");
1637 if !subprocess {
1638 test_env.add_config("git.subprocess = false");
1639 }
1640 work_dir.run_jj(["describe", "-m", "foo"]).success();
1641 work_dir.write_file("file", "contents");
1642 work_dir.run_jj(["git", "push", "--change", "@"]).success();
1643 work_dir.write_file("file", "modified");
1644 work_dir.run_jj(["git", "push", "--change", "@"]).success();
1645}
1646
1647#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1648#[test_case(true; "spawn a git subprocess for remote calls")]
1649fn test_git_push_conflict(subprocess: bool) {
1650 let test_env = TestEnvironment::default();
1651 set_up(&test_env);
1652 let work_dir = test_env.work_dir("local");
1653 if !subprocess {
1654 test_env.add_config("git.subprocess = false");
1655 }
1656 work_dir.write_file("file", "first");
1657 work_dir.run_jj(["commit", "-m", "first"]).success();
1658 work_dir.write_file("file", "second");
1659 work_dir.run_jj(["commit", "-m", "second"]).success();
1660 work_dir.write_file("file", "third");
1661 work_dir
1662 .run_jj(["rebase", "-r", "@", "-d", "@--"])
1663 .success();
1664 work_dir
1665 .run_jj(["bookmark", "create", "-r@", "my-bookmark"])
1666 .success();
1667 work_dir.run_jj(["describe", "-m", "third"]).success();
1668 let output = work_dir.run_jj(["git", "push", "--all"]);
1669 insta::allow_duplicates! {
1670 insta::assert_snapshot!(output, @r"
1671 ------- stderr -------
1672 Error: Won't push commit e2221a796300 since it has conflicts
1673 Hint: Rejected commit: yostqsxw e2221a79 my-bookmark | (conflict) third
1674 [EOF]
1675 [exit status: 1]
1676 ");
1677 }
1678}
1679
1680#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1681#[test_case(true; "spawn a git subprocess for remote calls")]
1682fn test_git_push_no_description(subprocess: bool) {
1683 let test_env = TestEnvironment::default();
1684 set_up(&test_env);
1685 let work_dir = test_env.work_dir("local");
1686 if !subprocess {
1687 test_env.add_config("git.subprocess = false");
1688 }
1689 work_dir
1690 .run_jj(["bookmark", "create", "-r@", "my-bookmark"])
1691 .success();
1692 work_dir.run_jj(["describe", "-m="]).success();
1693 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark", "my-bookmark"]);
1694 insta::allow_duplicates! {
1695 insta::assert_snapshot!(output, @r"
1696 ------- stderr -------
1697 Error: Won't push commit 5b36783cd11c since it has no description
1698 Hint: Rejected commit: yqosqzyt 5b36783c my-bookmark | (empty) (no description set)
1699 [EOF]
1700 [exit status: 1]
1701 ");
1702 }
1703 work_dir
1704 .run_jj([
1705 "git",
1706 "push",
1707 "--allow-new",
1708 "--bookmark",
1709 "my-bookmark",
1710 "--allow-empty-description",
1711 ])
1712 .success();
1713}
1714
1715#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1716#[test_case(true; "spawn a git subprocess for remote calls")]
1717fn test_git_push_no_description_in_immutable(subprocess: bool) {
1718 let test_env = TestEnvironment::default();
1719 set_up(&test_env);
1720 let work_dir = test_env.work_dir("local");
1721 if !subprocess {
1722 test_env.add_config("git.subprocess = false");
1723 }
1724 work_dir
1725 .run_jj(["bookmark", "create", "-r@", "imm"])
1726 .success();
1727 work_dir.run_jj(["describe", "-m="]).success();
1728 work_dir.run_jj(["new", "-m", "foo"]).success();
1729 work_dir.write_file("file", "contents");
1730 work_dir
1731 .run_jj(["bookmark", "create", "-r@", "my-bookmark"])
1732 .success();
1733
1734 let output = work_dir.run_jj([
1735 "git",
1736 "push",
1737 "--allow-new",
1738 "--bookmark=my-bookmark",
1739 "--dry-run",
1740 ]);
1741 insta::allow_duplicates! {
1742 insta::assert_snapshot!(output, @r"
1743 ------- stderr -------
1744 Error: Won't push commit 5b36783cd11c since it has no description
1745 Hint: Rejected commit: yqosqzyt 5b36783c imm | (empty) (no description set)
1746 [EOF]
1747 [exit status: 1]
1748 ");
1749 }
1750
1751 test_env.add_config(r#"revset-aliases."immutable_heads()" = "imm""#);
1752 let output = work_dir.run_jj([
1753 "git",
1754 "push",
1755 "--allow-new",
1756 "--bookmark=my-bookmark",
1757 "--dry-run",
1758 ]);
1759 insta::allow_duplicates! {
1760 insta::assert_snapshot!(output, @r"
1761 ------- stderr -------
1762 Changes to push to origin:
1763 Add bookmark my-bookmark to ea7373507ad9
1764 Dry-run requested, not pushing.
1765 [EOF]
1766 ");
1767 }
1768}
1769
1770#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1771#[test_case(true; "spawn a git subprocess for remote calls")]
1772fn test_git_push_missing_author(subprocess: bool) {
1773 let test_env = TestEnvironment::default();
1774 set_up(&test_env);
1775 let work_dir = test_env.work_dir("local");
1776 if !subprocess {
1777 test_env.add_config("git.subprocess = false");
1778 }
1779 let run_without_var = |var: &str, args: &[&str]| {
1780 work_dir
1781 .run_jj_with(|cmd| cmd.args(args).env_remove(var))
1782 .success();
1783 };
1784 run_without_var("JJ_USER", &["new", "root()", "-m=initial"]);
1785 run_without_var("JJ_USER", &["bookmark", "create", "-r@", "missing-name"]);
1786 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark", "missing-name"]);
1787 insta::allow_duplicates! {
1788 insta::assert_snapshot!(output, @r"
1789 ------- stderr -------
1790 Error: Won't push commit 944313939bbd since it has no author and/or committer set
1791 Hint: Rejected commit: vruxwmqv 94431393 missing-name | (empty) initial
1792 [EOF]
1793 [exit status: 1]
1794 ");
1795 }
1796 run_without_var("JJ_EMAIL", &["new", "root()", "-m=initial"]);
1797 run_without_var("JJ_EMAIL", &["bookmark", "create", "-r@", "missing-email"]);
1798 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark=missing-email"]);
1799 insta::allow_duplicates! {
1800 insta::assert_snapshot!(output, @r"
1801 ------- stderr -------
1802 Error: Won't push commit 59354714f789 since it has no author and/or committer set
1803 Hint: Rejected commit: kpqxywon 59354714 missing-email | (empty) initial
1804 [EOF]
1805 [exit status: 1]
1806 ");
1807 }
1808}
1809
1810#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1811#[test_case(true; "spawn a git subprocess for remote calls")]
1812fn test_git_push_missing_author_in_immutable(subprocess: bool) {
1813 let test_env = TestEnvironment::default();
1814 set_up(&test_env);
1815 let work_dir = test_env.work_dir("local");
1816 if !subprocess {
1817 test_env.add_config("git.subprocess = false");
1818 }
1819 let run_without_var = |var: &str, args: &[&str]| {
1820 work_dir
1821 .run_jj_with(|cmd| cmd.args(args).env_remove(var))
1822 .success();
1823 };
1824 run_without_var("JJ_USER", &["new", "root()", "-m=no author name"]);
1825 run_without_var("JJ_EMAIL", &["new", "-m=no author email"]);
1826 work_dir
1827 .run_jj(["bookmark", "create", "-r@", "imm"])
1828 .success();
1829 work_dir.run_jj(["new", "-m", "foo"]).success();
1830 work_dir.write_file("file", "contents");
1831 work_dir
1832 .run_jj(["bookmark", "create", "-r@", "my-bookmark"])
1833 .success();
1834
1835 let output = work_dir.run_jj([
1836 "git",
1837 "push",
1838 "--allow-new",
1839 "--bookmark=my-bookmark",
1840 "--dry-run",
1841 ]);
1842 insta::allow_duplicates! {
1843 insta::assert_snapshot!(output, @r"
1844 ------- stderr -------
1845 Error: Won't push commit 011f740bf8b5 since it has no author and/or committer set
1846 Hint: Rejected commit: yostqsxw 011f740b imm | (empty) no author email
1847 [EOF]
1848 [exit status: 1]
1849 ");
1850 }
1851
1852 test_env.add_config(r#"revset-aliases."immutable_heads()" = "imm""#);
1853 let output = work_dir.run_jj([
1854 "git",
1855 "push",
1856 "--allow-new",
1857 "--bookmark=my-bookmark",
1858 "--dry-run",
1859 ]);
1860 insta::allow_duplicates! {
1861 insta::assert_snapshot!(output, @r"
1862 ------- stderr -------
1863 Changes to push to origin:
1864 Add bookmark my-bookmark to 68fdae89de4f
1865 Dry-run requested, not pushing.
1866 [EOF]
1867 ");
1868 }
1869}
1870
1871#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1872#[test_case(true; "spawn a git subprocess for remote calls")]
1873fn test_git_push_missing_committer(subprocess: bool) {
1874 let test_env = TestEnvironment::default();
1875 set_up(&test_env);
1876 let work_dir = test_env.work_dir("local");
1877 if !subprocess {
1878 test_env.add_config("git.subprocess = false");
1879 }
1880 let run_without_var = |var: &str, args: &[&str]| {
1881 work_dir
1882 .run_jj_with(|cmd| cmd.args(args).env_remove(var))
1883 .success();
1884 };
1885 work_dir
1886 .run_jj(["bookmark", "create", "-r@", "missing-name"])
1887 .success();
1888 run_without_var("JJ_USER", &["describe", "-m=no committer name"]);
1889 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark=missing-name"]);
1890 insta::allow_duplicates! {
1891 insta::assert_snapshot!(output, @r"
1892 ------- stderr -------
1893 Error: Won't push commit 4fd190283d1a since it has no author and/or committer set
1894 Hint: Rejected commit: yqosqzyt 4fd19028 missing-name | (empty) no committer name
1895 [EOF]
1896 [exit status: 1]
1897 ");
1898 }
1899 work_dir.run_jj(["new", "root()"]).success();
1900 work_dir
1901 .run_jj(["bookmark", "create", "-r@", "missing-email"])
1902 .success();
1903 run_without_var("JJ_EMAIL", &["describe", "-m=no committer email"]);
1904 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark=missing-email"]);
1905 insta::allow_duplicates! {
1906 insta::assert_snapshot!(output, @r"
1907 ------- stderr -------
1908 Error: Won't push commit eab97428a6ec since it has no author and/or committer set
1909 Hint: Rejected commit: kpqxywon eab97428 missing-email | (empty) no committer email
1910 [EOF]
1911 [exit status: 1]
1912 ");
1913 }
1914
1915 // Test message when there are multiple reasons (missing committer and
1916 // description)
1917 run_without_var("JJ_EMAIL", &["describe", "-m=", "missing-email"]);
1918 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark=missing-email"]);
1919 insta::allow_duplicates! {
1920 insta::assert_snapshot!(output, @r"
1921 ------- stderr -------
1922 Error: Won't push commit 1143ed607f54 since it has no description and it has no author and/or committer set
1923 Hint: Rejected commit: kpqxywon 1143ed60 missing-email | (empty) (no description set)
1924 [EOF]
1925 [exit status: 1]
1926 ");
1927 }
1928}
1929
1930#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1931#[test_case(true; "spawn a git subprocess for remote calls")]
1932fn test_git_push_missing_committer_in_immutable(subprocess: bool) {
1933 let test_env = TestEnvironment::default();
1934 set_up(&test_env);
1935 let work_dir = test_env.work_dir("local");
1936 if !subprocess {
1937 test_env.add_config("git.subprocess = false");
1938 }
1939 let run_without_var = |var: &str, args: &[&str]| {
1940 work_dir
1941 .run_jj_with(|cmd| cmd.args(args).env_remove(var))
1942 .success();
1943 };
1944 run_without_var("JJ_USER", &["describe", "-m=no committer name"]);
1945 work_dir.run_jj(["new"]).success();
1946 run_without_var("JJ_EMAIL", &["describe", "-m=no committer email"]);
1947 work_dir
1948 .run_jj(["bookmark", "create", "-r@", "imm"])
1949 .success();
1950 work_dir.run_jj(["new", "-m", "foo"]).success();
1951 work_dir.write_file("file", "contents");
1952 work_dir
1953 .run_jj(["bookmark", "create", "-r@", "my-bookmark"])
1954 .success();
1955
1956 let output = work_dir.run_jj([
1957 "git",
1958 "push",
1959 "--allow-new",
1960 "--bookmark=my-bookmark",
1961 "--dry-run",
1962 ]);
1963 insta::allow_duplicates! {
1964 insta::assert_snapshot!(output, @r"
1965 ------- stderr -------
1966 Error: Won't push commit 7e61dc727a8f since it has no author and/or committer set
1967 Hint: Rejected commit: yostqsxw 7e61dc72 imm | (empty) no committer email
1968 [EOF]
1969 [exit status: 1]
1970 ");
1971 }
1972
1973 test_env.add_config(r#"revset-aliases."immutable_heads()" = "imm""#);
1974 let output = work_dir.run_jj([
1975 "git",
1976 "push",
1977 "--allow-new",
1978 "--bookmark=my-bookmark",
1979 "--dry-run",
1980 ]);
1981 insta::allow_duplicates! {
1982 insta::assert_snapshot!(output, @r"
1983 ------- stderr -------
1984 Changes to push to origin:
1985 Add bookmark my-bookmark to c79f85e90b4a
1986 Dry-run requested, not pushing.
1987 [EOF]
1988 ");
1989 }
1990}
1991
1992#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
1993#[test_case(true; "spawn a git subprocess for remote calls")]
1994fn test_git_push_deleted(subprocess: bool) {
1995 let test_env = TestEnvironment::default();
1996 set_up(&test_env);
1997 let work_dir = test_env.work_dir("local");
1998 if !subprocess {
1999 test_env.add_config("git.subprocess = false");
2000 }
2001
2002 work_dir
2003 .run_jj(["bookmark", "delete", "bookmark1"])
2004 .success();
2005 let output = work_dir.run_jj(["git", "push", "--deleted"]);
2006 insta::allow_duplicates! {
2007 insta::assert_snapshot!(output, @r"
2008 ------- stderr -------
2009 Changes to push to origin:
2010 Delete bookmark bookmark1 from d13ecdbda2a2
2011 [EOF]
2012 ");
2013 }
2014 let output = work_dir.run_jj(["log", "-rall()"]);
2015 insta::allow_duplicates! {
2016 insta::assert_snapshot!(output, @r"
2017 @ yqosqzyt test.user@example.com 2001-02-03 08:05:13 5b36783c
2018 │ (empty) (no description set)
2019 │ ○ rlzusymt test.user@example.com 2001-02-03 08:05:10 bookmark2 8476341e
2020 ├─╯ (empty) description 2
2021 │ ○ xtvrqkyv test.user@example.com 2001-02-03 08:05:08 d13ecdbd
2022 ├─╯ (empty) description 1
2023 ◆ zzzzzzzz root() 00000000
2024 [EOF]
2025 ");
2026 }
2027 let output = work_dir.run_jj(["git", "push", "--deleted"]);
2028 insta::allow_duplicates! {
2029 insta::assert_snapshot!(output, @r"
2030 ------- stderr -------
2031 Nothing changed.
2032 [EOF]
2033 ");
2034 }
2035}
2036
2037#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
2038#[test_case(true; "spawn a git subprocess for remote calls")]
2039fn test_git_push_conflicting_bookmarks(subprocess: bool) {
2040 let test_env = TestEnvironment::default();
2041 set_up(&test_env);
2042 let work_dir = test_env.work_dir("local");
2043 if !subprocess {
2044 test_env.add_config("git.subprocess = false");
2045 }
2046 test_env.add_config("git.auto-local-bookmark = true");
2047 let git_repo = {
2048 let mut git_repo_path = work_dir.root().to_owned();
2049 git_repo_path.extend([".jj", "repo", "store", "git"]);
2050 git::open(&git_repo_path)
2051 };
2052
2053 // Forget remote ref, move local ref, then fetch to create conflict.
2054 git_repo
2055 .find_reference("refs/remotes/origin/bookmark2")
2056 .unwrap()
2057 .delete()
2058 .unwrap();
2059 work_dir.run_jj(["git", "import"]).success();
2060 work_dir
2061 .run_jj(["new", "root()", "-m=description 3"])
2062 .success();
2063 work_dir
2064 .run_jj(["bookmark", "create", "-r@", "bookmark2"])
2065 .success();
2066 work_dir.run_jj(["git", "fetch"]).success();
2067 insta::allow_duplicates! {
2068 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
2069 bookmark1: xtvrqkyv d13ecdbd (empty) description 1
2070 @origin: xtvrqkyv d13ecdbd (empty) description 1
2071 bookmark2 (conflicted):
2072 + yostqsxw 8e670e2d (empty) description 3
2073 + rlzusymt 8476341e (empty) description 2
2074 @origin (behind by 1 commits): rlzusymt 8476341e (empty) description 2
2075 [EOF]
2076 ");
2077 }
2078
2079 let bump_bookmark1 = || {
2080 work_dir.run_jj(["new", "bookmark1", "-m=bump"]).success();
2081 work_dir
2082 .run_jj(["bookmark", "set", "bookmark1", "-r@"])
2083 .success();
2084 };
2085
2086 // Conflicting bookmark at @
2087 let output = work_dir.run_jj(["git", "push", "--allow-new"]);
2088 insta::allow_duplicates! {
2089 insta::assert_snapshot!(output, @r"
2090 ------- stderr -------
2091 Warning: Bookmark bookmark2 is conflicted
2092 Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up.
2093 Nothing changed.
2094 [EOF]
2095 ");
2096 }
2097
2098 // --bookmark should be blocked by conflicting bookmark
2099 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark", "bookmark2"]);
2100 insta::allow_duplicates! {
2101 insta::assert_snapshot!(output, @r"
2102 ------- stderr -------
2103 Error: Bookmark bookmark2 is conflicted
2104 Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up.
2105 [EOF]
2106 [exit status: 1]
2107 ");
2108 }
2109
2110 // --all shouldn't be blocked by conflicting bookmark
2111 bump_bookmark1();
2112 let output = work_dir.run_jj(["git", "push", "--all"]);
2113 insta::allow_duplicates! {
2114 insta::assert_snapshot!(output, @r"
2115 ------- stderr -------
2116 Warning: Bookmark bookmark2 is conflicted
2117 Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up.
2118 Changes to push to origin:
2119 Move forward bookmark bookmark1 from d13ecdbda2a2 to 8df52121b022
2120 [EOF]
2121 ");
2122 }
2123
2124 // --revisions shouldn't be blocked by conflicting bookmark
2125 bump_bookmark1();
2126 let output = work_dir.run_jj(["git", "push", "--allow-new", "-rall()"]);
2127 insta::allow_duplicates! {
2128 insta::assert_snapshot!(output, @r"
2129 ------- stderr -------
2130 Warning: Bookmark bookmark2 is conflicted
2131 Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up.
2132 Changes to push to origin:
2133 Move forward bookmark bookmark1 from 8df52121b022 to 345e1f64a64d
2134 [EOF]
2135 ");
2136 }
2137}
2138
2139#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
2140#[test_case(true; "spawn a git subprocess for remote calls")]
2141fn test_git_push_deleted_untracked(subprocess: bool) {
2142 let test_env = TestEnvironment::default();
2143 set_up(&test_env);
2144 let work_dir = test_env.work_dir("local");
2145 if !subprocess {
2146 test_env.add_config("git.subprocess = false");
2147 }
2148
2149 // Absent local bookmark shouldn't be considered "deleted" compared to
2150 // non-tracking remote bookmark.
2151 work_dir
2152 .run_jj(["bookmark", "delete", "bookmark1"])
2153 .success();
2154 work_dir
2155 .run_jj(["bookmark", "untrack", "bookmark1@origin"])
2156 .success();
2157 let output = work_dir.run_jj(["git", "push", "--deleted"]);
2158 insta::allow_duplicates! {
2159 insta::assert_snapshot!(output, @r"
2160 ------- stderr -------
2161 Nothing changed.
2162 [EOF]
2163 ");
2164 }
2165 let output = work_dir.run_jj(["git", "push", "--bookmark=bookmark1"]);
2166 insta::allow_duplicates! {
2167 insta::assert_snapshot!(output, @r"
2168 ------- stderr -------
2169 Error: No such bookmark: bookmark1
2170 [EOF]
2171 [exit status: 1]
2172 ");
2173 }
2174}
2175
2176#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
2177#[test_case(true; "spawn a git subprocess for remote calls")]
2178fn test_git_push_tracked_vs_all(subprocess: bool) {
2179 let test_env = TestEnvironment::default();
2180 set_up(&test_env);
2181 let work_dir = test_env.work_dir("local");
2182 if !subprocess {
2183 test_env.add_config("git.subprocess = false");
2184 }
2185 work_dir
2186 .run_jj(["new", "bookmark1", "-mmoved bookmark1"])
2187 .success();
2188 work_dir
2189 .run_jj(["bookmark", "set", "bookmark1", "-r@"])
2190 .success();
2191 work_dir
2192 .run_jj(["new", "bookmark2", "-mmoved bookmark2"])
2193 .success();
2194 work_dir
2195 .run_jj(["bookmark", "delete", "bookmark2"])
2196 .success();
2197 work_dir
2198 .run_jj(["bookmark", "untrack", "bookmark1@origin"])
2199 .success();
2200 work_dir
2201 .run_jj(["bookmark", "create", "-r@", "bookmark3"])
2202 .success();
2203 insta::allow_duplicates! {
2204 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
2205 bookmark1: vruxwmqv db059e3f (empty) moved bookmark1
2206 bookmark1@origin: xtvrqkyv d13ecdbd (empty) description 1
2207 bookmark2 (deleted)
2208 @origin: rlzusymt 8476341e (empty) description 2
2209 bookmark3: znkkpsqq 1aa4f1f2 (empty) moved bookmark2
2210 [EOF]
2211 ");
2212 }
2213
2214 // At this point, only bookmark2 is still tracked.
2215 // `jj git push --tracked --deleted` would try to push it and no other
2216 // bookmarks.
2217 let output = work_dir.run_jj(["git", "push", "--tracked", "--dry-run"]);
2218 insta::allow_duplicates! {
2219 insta::assert_snapshot!(output, @r"
2220 ------- stderr -------
2221 Warning: Refusing to push deleted bookmark bookmark2
2222 Hint: Push deleted bookmarks with --deleted or forget the bookmark to suppress this warning.
2223 Nothing changed.
2224 [EOF]
2225 ");
2226 }
2227 let output = work_dir.run_jj(["git", "push", "--tracked", "--deleted", "--dry-run"]);
2228 insta::allow_duplicates! {
2229 insta::assert_snapshot!(output, @r"
2230 ------- stderr -------
2231 Changes to push to origin:
2232 Delete bookmark bookmark2 from 8476341eb395
2233 Dry-run requested, not pushing.
2234 [EOF]
2235 ");
2236 }
2237
2238 // Untrack the last remaining tracked bookmark.
2239 work_dir
2240 .run_jj(["bookmark", "untrack", "bookmark2@origin"])
2241 .success();
2242 insta::allow_duplicates! {
2243 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
2244 bookmark1: vruxwmqv db059e3f (empty) moved bookmark1
2245 bookmark1@origin: xtvrqkyv d13ecdbd (empty) description 1
2246 bookmark2@origin: rlzusymt 8476341e (empty) description 2
2247 bookmark3: znkkpsqq 1aa4f1f2 (empty) moved bookmark2
2248 [EOF]
2249 ");
2250 }
2251
2252 // Now, no bookmarks are tracked. --tracked does not push anything
2253 let output = work_dir.run_jj(["git", "push", "--tracked"]);
2254 insta::allow_duplicates! {
2255 insta::assert_snapshot!(output, @r"
2256 ------- stderr -------
2257 Nothing changed.
2258 [EOF]
2259 ");
2260 }
2261
2262 // All bookmarks are still untracked.
2263 // - --all tries to push bookmark1, but fails because a bookmark with the same
2264 // name exist on the remote.
2265 // - --all succeeds in pushing bookmark3, since there is no bookmark of the same
2266 // name on the remote.
2267 // - It does not try to push bookmark2.
2268 //
2269 // TODO: Not trying to push bookmark2 could be considered correct, or perhaps
2270 // we want to consider this as a deletion of the bookmark that failed because
2271 // the bookmark was untracked. In the latter case, an error message should be
2272 // printed. Some considerations:
2273 // - Whatever we do should be consistent with what `jj bookmark list` does; it
2274 // currently does *not* list bookmarks like bookmark2 as "about to be
2275 // deleted", as can be seen above.
2276 // - We could consider showing some hint on `jj bookmark untrack
2277 // bookmark2@origin` instead of showing an error here.
2278 let output = work_dir.run_jj(["git", "push", "--all"]);
2279 insta::allow_duplicates! {
2280 insta::assert_snapshot!(output, @r"
2281 ------- stderr -------
2282 Warning: Non-tracking remote bookmark bookmark1@origin exists
2283 Hint: Run `jj bookmark track bookmark1@origin` to import the remote bookmark.
2284 Changes to push to origin:
2285 Add bookmark bookmark3 to 1aa4f1f2ef7f
2286 [EOF]
2287 ");
2288 }
2289}
2290
2291#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
2292#[test_case(true; "spawn a git subprocess for remote calls")]
2293fn test_git_push_moved_forward_untracked(subprocess: bool) {
2294 let test_env = TestEnvironment::default();
2295 set_up(&test_env);
2296 let work_dir = test_env.work_dir("local");
2297 if !subprocess {
2298 test_env.add_config("git.subprocess = false");
2299 }
2300
2301 work_dir
2302 .run_jj(["new", "bookmark1", "-mmoved bookmark1"])
2303 .success();
2304 work_dir
2305 .run_jj(["bookmark", "set", "bookmark1", "-r@"])
2306 .success();
2307 work_dir
2308 .run_jj(["bookmark", "untrack", "bookmark1@origin"])
2309 .success();
2310 let output = work_dir.run_jj(["git", "push", "--allow-new"]);
2311 insta::allow_duplicates! {
2312 insta::assert_snapshot!(output, @r"
2313 ------- stderr -------
2314 Warning: Non-tracking remote bookmark bookmark1@origin exists
2315 Hint: Run `jj bookmark track bookmark1@origin` to import the remote bookmark.
2316 Nothing changed.
2317 [EOF]
2318 ");
2319 }
2320}
2321
2322#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
2323#[test_case(true; "spawn a git subprocess for remote calls")]
2324fn test_git_push_moved_sideways_untracked(subprocess: bool) {
2325 let test_env = TestEnvironment::default();
2326 set_up(&test_env);
2327 let work_dir = test_env.work_dir("local");
2328 if !subprocess {
2329 test_env.add_config("git.subprocess = false");
2330 }
2331
2332 work_dir
2333 .run_jj(["new", "root()", "-mmoved bookmark1"])
2334 .success();
2335 work_dir
2336 .run_jj(["bookmark", "set", "--allow-backwards", "bookmark1", "-r@"])
2337 .success();
2338 work_dir
2339 .run_jj(["bookmark", "untrack", "bookmark1@origin"])
2340 .success();
2341 let output = work_dir.run_jj(["git", "push", "--allow-new"]);
2342 insta::allow_duplicates! {
2343 insta::assert_snapshot!(output, @r"
2344 ------- stderr -------
2345 Warning: Non-tracking remote bookmark bookmark1@origin exists
2346 Hint: Run `jj bookmark track bookmark1@origin` to import the remote bookmark.
2347 Nothing changed.
2348 [EOF]
2349 ");
2350 }
2351}
2352
2353#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
2354#[test_case(true; "spawn a git subprocess for remote calls")]
2355fn test_git_push_to_remote_named_git(subprocess: bool) {
2356 let test_env = TestEnvironment::default();
2357 set_up(&test_env);
2358 let work_dir = test_env.work_dir("local");
2359 if !subprocess {
2360 test_env.add_config("git.subprocess = false");
2361 }
2362 let git_repo_path = {
2363 let mut git_repo_path = work_dir.root().to_owned();
2364 git_repo_path.extend([".jj", "repo", "store", "git"]);
2365 git_repo_path
2366 };
2367 git::rename_remote(&git_repo_path, "origin", "git");
2368
2369 let output = work_dir.run_jj(["git", "push", "--all", "--remote=git"]);
2370 insta::allow_duplicates! {
2371 insta::assert_snapshot!(output, @r"
2372 ------- stderr -------
2373 Changes to push to git:
2374 Add bookmark bookmark1 to d13ecdbda2a2
2375 Add bookmark bookmark2 to 8476341eb395
2376 Error: Git remote named 'git' is reserved for local Git repository
2377 Hint: Run `jj git remote rename` to give a different name.
2378 [EOF]
2379 [exit status: 1]
2380 ");
2381 }
2382}
2383
2384#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))]
2385#[test_case(true; "spawn a git subprocess for remote calls")]
2386fn test_git_push_to_remote_with_slashes(subprocess: bool) {
2387 let test_env = TestEnvironment::default();
2388 set_up(&test_env);
2389 let work_dir = test_env.work_dir("local");
2390 if !subprocess {
2391 test_env.add_config("git.subprocess = false");
2392 }
2393 let git_repo_path = {
2394 let mut git_repo_path = work_dir.root().to_owned();
2395 git_repo_path.extend([".jj", "repo", "store", "git"]);
2396 git_repo_path
2397 };
2398 git::rename_remote(&git_repo_path, "origin", "slash/origin");
2399
2400 let output = work_dir.run_jj(["git", "push", "--all", "--remote=slash/origin"]);
2401 insta::allow_duplicates! {
2402 insta::assert_snapshot!(output, @r"
2403 ------- stderr -------
2404 Changes to push to slash/origin:
2405 Add bookmark bookmark1 to d13ecdbda2a2
2406 Add bookmark bookmark2 to 8476341eb395
2407 Error: Git remotes with slashes are incompatible with jj: slash/origin
2408 Hint: Run `jj git remote rename` to give a different name.
2409 [EOF]
2410 [exit status: 1]
2411 ");
2412 }
2413}
2414
2415#[test]
2416fn test_git_push_sign_on_push() {
2417 let test_env = TestEnvironment::default();
2418 set_up(&test_env);
2419 let work_dir = test_env.work_dir("local");
2420 let template = r#"
2421 separate("\n",
2422 description.first_line(),
2423 if(signature,
2424 separate(", ",
2425 "Signature: " ++ signature.display(),
2426 "Status: " ++ signature.status(),
2427 "Key: " ++ signature.key(),
2428 )
2429 )
2430 )
2431 "#;
2432 work_dir
2433 .run_jj(["new", "bookmark2", "-m", "commit to be signed 1"])
2434 .success();
2435 work_dir
2436 .run_jj(["new", "-m", "commit to be signed 2"])
2437 .success();
2438 work_dir
2439 .run_jj(["bookmark", "set", "bookmark2", "-r@"])
2440 .success();
2441 work_dir
2442 .run_jj(["new", "-m", "commit which should not be signed 1"])
2443 .success();
2444 work_dir
2445 .run_jj(["new", "-m", "commit which should not be signed 2"])
2446 .success();
2447 // There should be no signed commits initially
2448 let output = work_dir.run_jj(["log", "-T", template]);
2449 insta::assert_snapshot!(output, @r"
2450 @ commit which should not be signed 2
2451 ○ commit which should not be signed 1
2452 ○ commit to be signed 2
2453 ○ commit to be signed 1
2454 ○ description 2
2455 │ ○ description 1
2456 ├─╯
2457 ◆
2458 [EOF]
2459 ");
2460 test_env.add_config(
2461 r#"
2462 signing.backend = "test"
2463 signing.key = "impeccable"
2464 git.sign-on-push = true
2465 "#,
2466 );
2467 let output = work_dir.run_jj(["git", "push", "--dry-run"]);
2468 insta::assert_snapshot!(output, @r"
2469 ------- stderr -------
2470 Changes to push to origin:
2471 Move forward bookmark bookmark2 from 8476341eb395 to 8710e91a14a1
2472 Dry-run requested, not pushing.
2473 [EOF]
2474 ");
2475 // There should be no signed commits after performing a dry run
2476 let output = work_dir.run_jj(["log", "-T", template]);
2477 insta::assert_snapshot!(output, @r"
2478 @ commit which should not be signed 2
2479 ○ commit which should not be signed 1
2480 ○ commit to be signed 2
2481 ○ commit to be signed 1
2482 ○ description 2
2483 │ ○ description 1
2484 ├─╯
2485 ◆
2486 [EOF]
2487 ");
2488 let output = work_dir.run_jj(["git", "push"]);
2489 insta::assert_snapshot!(output, @r"
2490 ------- stderr -------
2491 Updated signatures of 2 commits
2492 Rebased 2 descendant commits
2493 Changes to push to origin:
2494 Move forward bookmark bookmark2 from 8476341eb395 to a6259c482040
2495 Working copy (@) now at: kmkuslsw b5f47345 (empty) commit which should not be signed 2
2496 Parent commit (@-) : kpqxywon 90df08d3 (empty) commit which should not be signed 1
2497 [EOF]
2498 ");
2499 // Only commits which are being pushed should be signed
2500 let output = work_dir.run_jj(["log", "-T", template]);
2501 insta::assert_snapshot!(output, @r"
2502 @ commit which should not be signed 2
2503 ○ commit which should not be signed 1
2504 ○ commit to be signed 2
2505 │ Signature: test-display, Status: good, Key: impeccable
2506 ○ commit to be signed 1
2507 │ Signature: test-display, Status: good, Key: impeccable
2508 ○ description 2
2509 │ ○ description 1
2510 ├─╯
2511 ◆
2512 [EOF]
2513 ");
2514
2515 // Immutable commits should not be signed
2516 let output = work_dir.run_jj([
2517 "bookmark",
2518 "create",
2519 "bookmark3",
2520 "-r",
2521 "description('commit which should not be signed 1')",
2522 ]);
2523 insta::assert_snapshot!(output, @r"
2524 ------- stderr -------
2525 Created 1 bookmarks pointing to kpqxywon 90df08d3 bookmark3 | (empty) commit which should not be signed 1
2526 [EOF]
2527 ");
2528 let output = work_dir.run_jj(["bookmark", "move", "bookmark2", "--to", "bookmark3"]);
2529 insta::assert_snapshot!(output, @r"
2530 ------- stderr -------
2531 Moved 1 bookmarks to kpqxywon 90df08d3 bookmark2* bookmark3 | (empty) commit which should not be signed 1
2532 [EOF]
2533 ");
2534 test_env.add_config(r#"revset-aliases."immutable_heads()" = "bookmark3""#);
2535 let output = work_dir.run_jj(["git", "push"]);
2536 insta::assert_snapshot!(output, @r"
2537 ------- stderr -------
2538 Warning: Refusing to create new remote bookmark bookmark3@origin
2539 Hint: Use --allow-new to push new bookmark. Use --remote to specify the remote to push to.
2540 Changes to push to origin:
2541 Move forward bookmark bookmark2 from a6259c482040 to 90df08d3d612
2542 [EOF]
2543 ");
2544 let output = work_dir.run_jj(["log", "-T", template, "-r", "::"]);
2545 insta::assert_snapshot!(output, @r"
2546 @ commit which should not be signed 2
2547 ◆ commit which should not be signed 1
2548 ◆ commit to be signed 2
2549 │ Signature: test-display, Status: good, Key: impeccable
2550 ◆ commit to be signed 1
2551 │ Signature: test-display, Status: good, Key: impeccable
2552 ◆ description 2
2553 │ ○ description 1
2554 ├─╯
2555 ◆
2556 [EOF]
2557 ");
2558}
2559
2560#[test]
2561fn test_git_push_rejected_by_remote() {
2562 let test_env = TestEnvironment::default();
2563 set_up(&test_env);
2564 let work_dir = test_env.work_dir("local");
2565 // show repo state
2566 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
2567 bookmark1: xtvrqkyv d13ecdbd (empty) description 1
2568 @origin: xtvrqkyv d13ecdbd (empty) description 1
2569 bookmark2: rlzusymt 8476341e (empty) description 2
2570 @origin: rlzusymt 8476341e (empty) description 2
2571 [EOF]
2572 ");
2573
2574 // create a hook on the remote that prevents pushing
2575 let hook_path = test_env
2576 .env_root()
2577 .join("origin")
2578 .join(".jj")
2579 .join("repo")
2580 .join("store")
2581 .join("git")
2582 .join("hooks")
2583 .join("update");
2584
2585 std::fs::write(&hook_path, "#!/bin/sh\nexit 1").unwrap();
2586 #[cfg(unix)]
2587 {
2588 use std::os::unix::fs::PermissionsExt as _;
2589
2590 std::fs::set_permissions(&hook_path, std::fs::Permissions::from_mode(0o700)).unwrap();
2591 }
2592
2593 // create new commit on top of bookmark1
2594 work_dir.run_jj(["new", "bookmark1"]).success();
2595 work_dir.write_file("file", "file");
2596 work_dir.run_jj(["describe", "-m=update"]).success();
2597
2598 // update bookmark
2599 work_dir.run_jj(["bookmark", "move", "bookmark1"]).success();
2600
2601 // push bookmark
2602 let output = work_dir.run_jj(["git", "push"]);
2603
2604 // The git remote sideband adds a dummy suffix of 8 spaces to attempt to clear
2605 // any leftover data. This is done to help with cases where the line is
2606 // rewritten.
2607 //
2608 // However, a common option in a lot of editors removes trailing whitespace.
2609 // This means that anyone with that option that opens this file would make the
2610 // following snapshot fail. Using the insta filter here normalizes the
2611 // output.
2612 let mut settings = insta::Settings::clone_current();
2613 settings.add_filter(r"\s*\n", "\n");
2614 settings.bind(|| {
2615 insta::assert_snapshot!(output, @r"
2616 ------- stderr -------
2617 Changes to push to origin:
2618 Move forward bookmark bookmark1 from d13ecdbda2a2 to dd5c09b30f9f
2619 remote: error: hook declined to update refs/heads/bookmark1
2620 Error: Failed to push some bookmarks
2621 Hint: The remote rejected the following updates:
2622 refs/heads/bookmark1 (reason: hook declined)
2623 Hint: Try checking if you have permission to push to all the bookmarks.
2624 [EOF]
2625 [exit status: 1]
2626 ");
2627 });
2628}
2629
2630#[must_use]
2631fn get_bookmark_output(work_dir: &TestWorkDir) -> CommandOutput {
2632 // --quiet to suppress deleted bookmarks hint
2633 work_dir.run_jj(["bookmark", "list", "--all-remotes", "--quiet"])
2634}
2635
2636// TODO: Remove with the `git.subprocess` setting.
2637#[cfg(not(feature = "git2"))]
2638#[test]
2639fn test_git_push_git2_warning() {
2640 let test_env = TestEnvironment::default();
2641 set_up(&test_env);
2642 let work_dir = test_env.work_dir("local");
2643 test_env.add_config("git.subprocess = false");
2644 work_dir
2645 .run_jj(["describe", "bookmark1", "-m", "modified bookmark1 commit"])
2646 .success();
2647 let output = work_dir.run_jj(["git", "push", "--all"]);
2648 insta::assert_snapshot!(output, @r#"
2649 ------- stderr -------
2650 Warning: Deprecated config: jj was compiled without `git.subprocess = false` support
2651 Changes to push to origin:
2652 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 0f8dc6560f32
2653 [EOF]
2654 "#);
2655}