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