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 std::path::PathBuf;
16
17use crate::common::{get_stderr_string, get_stdout_string, TestEnvironment};
18
19fn set_up() -> (TestEnvironment, PathBuf) {
20 let test_env = TestEnvironment::default();
21 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "origin"]);
22 let origin_path = test_env.env_root().join("origin");
23 let origin_git_repo_path = origin_path
24 .join(".jj")
25 .join("repo")
26 .join("store")
27 .join("git");
28
29 test_env.jj_cmd_ok(&origin_path, &["describe", "-m=description 1"]);
30 test_env.jj_cmd_ok(&origin_path, &["branch", "create", "branch1"]);
31 test_env.jj_cmd_ok(&origin_path, &["new", "root()", "-m=description 2"]);
32 test_env.jj_cmd_ok(&origin_path, &["branch", "create", "branch2"]);
33 test_env.jj_cmd_ok(&origin_path, &["git", "export"]);
34
35 test_env.jj_cmd_ok(
36 test_env.env_root(),
37 &[
38 "git",
39 "clone",
40 "--config-toml=git.auto-local-branch=true",
41 origin_git_repo_path.to_str().unwrap(),
42 "local",
43 ],
44 );
45 let workspace_root = test_env.env_root().join("local");
46 (test_env, workspace_root)
47}
48
49#[test]
50fn test_git_push_nothing() {
51 let (test_env, workspace_root) = set_up();
52 // Show the setup. `insta` has trouble if this is done inside `set_up()`
53 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]);
54 insta::assert_snapshot!(stdout, @r###"
55 branch1: lzmmnrxq 45a3aa29 (empty) description 1
56 @origin: lzmmnrxq 45a3aa29 (empty) description 1
57 branch2: rlzusymt 8476341e (empty) description 2
58 @origin: rlzusymt 8476341e (empty) description 2
59 "###);
60 // No branches to push yet
61 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--all"]);
62 insta::assert_snapshot!(stdout, @"");
63 insta::assert_snapshot!(stderr, @r###"
64 Nothing changed.
65 "###);
66}
67
68#[test]
69fn test_git_push_current_branch() {
70 let (test_env, workspace_root) = set_up();
71 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
72 // Update some branches. `branch1` is not a current branch, but `branch2` and
73 // `my-branch` are.
74 test_env.jj_cmd_ok(
75 &workspace_root,
76 &["describe", "branch1", "-m", "modified branch1 commit"],
77 );
78 test_env.jj_cmd_ok(&workspace_root, &["new", "branch2"]);
79 test_env.jj_cmd_ok(&workspace_root, &["branch", "set", "branch2"]);
80 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "my-branch"]);
81 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "foo"]);
82 // Check the setup
83 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]);
84 insta::assert_snapshot!(stdout, @r###"
85 branch1: lzmmnrxq 19e00bf6 (empty) modified branch1 commit
86 @origin (ahead by 1 commits, behind by 1 commits): lzmmnrxq hidden 45a3aa29 (empty) description 1
87 branch2: yostqsxw 10ee3363 (empty) foo
88 @origin (behind by 1 commits): rlzusymt 8476341e (empty) description 2
89 my-branch: yostqsxw 10ee3363 (empty) foo
90 "###);
91 // First dry-run. `branch1` should not get pushed.
92 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--dry-run"]);
93 insta::assert_snapshot!(stdout, @"");
94 insta::assert_snapshot!(stderr, @r###"
95 Branch changes to push to origin:
96 Move branch branch2 from 8476341eb395 to 10ee3363b259
97 Add branch my-branch to 10ee3363b259
98 Dry-run requested, not pushing.
99 "###);
100 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
101 insta::assert_snapshot!(stdout, @"");
102 insta::assert_snapshot!(stderr, @r###"
103 Branch changes to push to origin:
104 Move branch branch2 from 8476341eb395 to 10ee3363b259
105 Add branch my-branch to 10ee3363b259
106 "###);
107 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]);
108 insta::assert_snapshot!(stdout, @r###"
109 branch1: lzmmnrxq 19e00bf6 (empty) modified branch1 commit
110 @origin (ahead by 1 commits, behind by 1 commits): lzmmnrxq hidden 45a3aa29 (empty) description 1
111 branch2: yostqsxw 10ee3363 (empty) foo
112 @origin: yostqsxw 10ee3363 (empty) foo
113 my-branch: yostqsxw 10ee3363 (empty) foo
114 @origin: yostqsxw 10ee3363 (empty) foo
115 "###);
116}
117
118#[test]
119fn test_git_push_parent_branch() {
120 let (test_env, workspace_root) = set_up();
121 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
122 test_env.jj_cmd_ok(&workspace_root, &["edit", "branch1"]);
123 test_env.jj_cmd_ok(
124 &workspace_root,
125 &["describe", "-m", "modified branch1 commit"],
126 );
127 test_env.jj_cmd_ok(&workspace_root, &["new", "-m", "non-empty description"]);
128 std::fs::write(workspace_root.join("file"), "file").unwrap();
129 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
130 insta::assert_snapshot!(stdout, @"");
131 insta::assert_snapshot!(stderr, @r###"
132 Branch changes to push to origin:
133 Force branch branch1 from 45a3aa29e907 to d47326d59ee1
134 "###);
135}
136
137#[test]
138fn test_git_push_no_matching_branch() {
139 let (test_env, workspace_root) = set_up();
140 test_env.jj_cmd_ok(&workspace_root, &["new"]);
141 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
142 insta::assert_snapshot!(stdout, @"");
143 insta::assert_snapshot!(stderr, @r###"
144 Warning: No branches found in the default push revset: remote_branches(remote=origin)..@
145 Nothing changed.
146 "###);
147}
148
149#[test]
150fn test_git_push_matching_branch_unchanged() {
151 let (test_env, workspace_root) = set_up();
152 test_env.jj_cmd_ok(&workspace_root, &["new", "branch1"]);
153 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
154 insta::assert_snapshot!(stdout, @"");
155 insta::assert_snapshot!(stderr, @r###"
156 Warning: No branches found in the default push revset: remote_branches(remote=origin)..@
157 Nothing changed.
158 "###);
159}
160
161/// Test that `jj git push` without arguments pushes a branch to the specified
162/// remote even if it's already up to date on another remote
163/// (`remote_branches(remote=<remote>)..@` vs. `remote_branches()..@`).
164#[test]
165fn test_git_push_other_remote_has_branch() {
166 let (test_env, workspace_root) = set_up();
167 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
168 // Create another remote (but actually the same)
169 let other_remote_path = test_env
170 .env_root()
171 .join("origin")
172 .join(".jj")
173 .join("repo")
174 .join("store")
175 .join("git");
176 test_env.jj_cmd_ok(
177 &workspace_root,
178 &[
179 "git",
180 "remote",
181 "add",
182 "other",
183 other_remote_path.to_str().unwrap(),
184 ],
185 );
186 // Modify branch1 and push it to `origin`
187 test_env.jj_cmd_ok(&workspace_root, &["edit", "branch1"]);
188 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m=modified"]);
189 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
190 insta::assert_snapshot!(stdout, @"");
191 insta::assert_snapshot!(stderr, @r###"
192 Branch changes to push to origin:
193 Force branch branch1 from 45a3aa29e907 to 50421a29358a
194 "###);
195 // Since it's already pushed to origin, nothing will happen if push again
196 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
197 insta::assert_snapshot!(stdout, @"");
198 insta::assert_snapshot!(stderr, @r###"
199 Warning: No branches found in the default push revset: remote_branches(remote=origin)..@
200 Nothing changed.
201 "###);
202 // But it will still get pushed to another remote
203 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--remote=other"]);
204 insta::assert_snapshot!(stdout, @"");
205 insta::assert_snapshot!(stderr, @r###"
206 Branch changes to push to other:
207 Add branch branch1 to 50421a29358a
208 "###);
209}
210
211#[test]
212fn test_git_push_not_fast_forward() {
213 let (test_env, workspace_root) = set_up();
214
215 // Move branch1 forward on the remote
216 let origin_path = test_env.env_root().join("origin");
217 test_env.jj_cmd_ok(&origin_path, &["new", "branch1", "-m=remote"]);
218 std::fs::write(origin_path.join("remote"), "remote").unwrap();
219 test_env.jj_cmd_ok(&origin_path, &["branch", "set", "branch1"]);
220 test_env.jj_cmd_ok(&origin_path, &["git", "export"]);
221
222 // Move branch1 forward to another commit locally
223 test_env.jj_cmd_ok(&workspace_root, &["new", "branch1", "-m=local"]);
224 std::fs::write(workspace_root.join("local"), "local").unwrap();
225 test_env.jj_cmd_ok(&workspace_root, &["branch", "set", "branch1"]);
226
227 // Pushing should fail
228 let assert = test_env
229 .jj_cmd(&workspace_root, &["git", "push"])
230 .assert()
231 .code(1);
232 insta::assert_snapshot!(get_stdout_string(&assert), @"");
233 insta::assert_snapshot!(get_stderr_string(&assert), @r###"
234 Branch changes to push to origin:
235 Move branch branch1 from 45a3aa29e907 to c35839cb8e8c
236 Error: The push conflicts with changes made on the remote (it is not fast-forwardable).
237 Hint: Try fetching from the remote, then make the branch point to where you want it to be, and push again.
238 "###);
239}
240
241#[test]
242fn test_git_push_locally_created_and_rewritten() {
243 let (test_env, workspace_root) = set_up();
244 // Ensure that remote branches aren't tracked automatically
245 test_env.add_config("git.auto-local-branch = false");
246
247 // Push locally-created branch
248 test_env.jj_cmd_ok(&workspace_root, &["new", "root()", "-mlocal 1"]);
249 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "my"]);
250 let (_stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
251 insta::assert_snapshot!(stderr, @r###"
252 Branch changes to push to origin:
253 Add branch my to fcc999921ce9
254 "###);
255
256 // Rewrite it and push again, which would fail if the pushed branch weren't
257 // set to "tracking"
258 test_env.jj_cmd_ok(&workspace_root, &["describe", "-mlocal 2"]);
259 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]);
260 insta::assert_snapshot!(stdout, @r###"
261 branch1: lzmmnrxq 45a3aa29 (empty) description 1
262 @origin: lzmmnrxq 45a3aa29 (empty) description 1
263 branch2: rlzusymt 8476341e (empty) description 2
264 @origin: rlzusymt 8476341e (empty) description 2
265 my: vruxwmqv bde1d2e4 (empty) local 2
266 @origin (ahead by 1 commits, behind by 1 commits): vruxwmqv hidden fcc99992 (empty) local 1
267 "###);
268 let (_stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
269 insta::assert_snapshot!(stderr, @r###"
270 Branch changes to push to origin:
271 Force branch my from fcc999921ce9 to bde1d2e44b2a
272 "###);
273}
274
275#[test]
276fn test_git_push_multiple() {
277 let (test_env, workspace_root) = set_up();
278 test_env.jj_cmd_ok(&workspace_root, &["branch", "delete", "branch1"]);
279 test_env.jj_cmd_ok(
280 &workspace_root,
281 &["branch", "set", "--allow-backwards", "branch2"],
282 );
283 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "my-branch"]);
284 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "foo"]);
285 // Check the setup
286 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]);
287 insta::assert_snapshot!(stdout, @r###"
288 branch1 (deleted)
289 @origin: lzmmnrxq 45a3aa29 (empty) description 1
290 (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this)
291 branch2: yqosqzyt 15dcdaa4 (empty) foo
292 @origin (ahead by 1 commits, behind by 1 commits): rlzusymt 8476341e (empty) description 2
293 my-branch: yqosqzyt 15dcdaa4 (empty) foo
294 "###);
295 // First dry-run
296 let (stdout, stderr) =
297 test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--all", "--dry-run"]);
298 insta::assert_snapshot!(stdout, @"");
299 insta::assert_snapshot!(stderr, @r###"
300 Branch changes to push to origin:
301 Delete branch branch1 from 45a3aa29e907
302 Force branch branch2 from 8476341eb395 to 15dcdaa4f12f
303 Add branch my-branch to 15dcdaa4f12f
304 Dry-run requested, not pushing.
305 "###);
306 // Dry run requesting two specific branches
307 let (stdout, stderr) = test_env.jj_cmd_ok(
308 &workspace_root,
309 &["git", "push", "-b=branch1", "-b=my-branch", "--dry-run"],
310 );
311 insta::assert_snapshot!(stdout, @"");
312 insta::assert_snapshot!(stderr, @r###"
313 Branch changes to push to origin:
314 Delete branch branch1 from 45a3aa29e907
315 Add branch my-branch to 15dcdaa4f12f
316 Dry-run requested, not pushing.
317 "###);
318 // Dry run requesting two specific branches twice
319 let (stdout, stderr) = test_env.jj_cmd_ok(
320 &workspace_root,
321 &[
322 "git",
323 "push",
324 "-b=branch1",
325 "-b=my-branch",
326 "-b=branch1",
327 "-b=glob:my-*",
328 "--dry-run",
329 ],
330 );
331 insta::assert_snapshot!(stdout, @"");
332 insta::assert_snapshot!(stderr, @r###"
333 Branch changes to push to origin:
334 Delete branch branch1 from 45a3aa29e907
335 Add branch my-branch to 15dcdaa4f12f
336 Dry-run requested, not pushing.
337 "###);
338 // Dry run with glob pattern
339 let (stdout, stderr) = test_env.jj_cmd_ok(
340 &workspace_root,
341 &["git", "push", "-b=glob:branch?", "--dry-run"],
342 );
343 insta::assert_snapshot!(stdout, @"");
344 insta::assert_snapshot!(stderr, @r###"
345 Branch changes to push to origin:
346 Delete branch branch1 from 45a3aa29e907
347 Force branch branch2 from 8476341eb395 to 15dcdaa4f12f
348 Dry-run requested, not pushing.
349 "###);
350
351 // Unmatched branch name is error
352 let stderr = test_env.jj_cmd_failure(&workspace_root, &["git", "push", "-b=foo"]);
353 insta::assert_snapshot!(stderr, @r###"
354 Error: No such branch: foo
355 "###);
356 let stderr = test_env.jj_cmd_failure(
357 &workspace_root,
358 &["git", "push", "-b=foo", "-b=glob:?branch"],
359 );
360 insta::assert_snapshot!(stderr, @r###"
361 Error: No matching branches for patterns: foo, ?branch
362 "###);
363
364 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--all"]);
365 insta::assert_snapshot!(stdout, @"");
366 insta::assert_snapshot!(stderr, @r###"
367 Branch changes to push to origin:
368 Delete branch branch1 from 45a3aa29e907
369 Force branch branch2 from 8476341eb395 to 15dcdaa4f12f
370 Add branch my-branch to 15dcdaa4f12f
371 "###);
372 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]);
373 insta::assert_snapshot!(stdout, @r###"
374 branch2: yqosqzyt 15dcdaa4 (empty) foo
375 @origin: yqosqzyt 15dcdaa4 (empty) foo
376 my-branch: yqosqzyt 15dcdaa4 (empty) foo
377 @origin: yqosqzyt 15dcdaa4 (empty) foo
378 "###);
379 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-rall()"]);
380 insta::assert_snapshot!(stdout, @r###"
381 @ yqosqzyt test.user@example.com 2001-02-03 08:05:17 branch2 my-branch 15dcdaa4
382 │ (empty) foo
383 │ ◉ rlzusymt test.user@example.com 2001-02-03 08:05:10 8476341e
384 ├─╯ (empty) description 2
385 │ ◉ lzmmnrxq test.user@example.com 2001-02-03 08:05:08 45a3aa29
386 ├─╯ (empty) description 1
387 ◉ zzzzzzzz root() 00000000
388 "###);
389}
390
391#[test]
392fn test_git_push_changes() {
393 let (test_env, workspace_root) = set_up();
394 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "foo"]);
395 std::fs::write(workspace_root.join("file"), "contents").unwrap();
396 test_env.jj_cmd_ok(&workspace_root, &["new", "-m", "bar"]);
397 std::fs::write(workspace_root.join("file"), "modified").unwrap();
398
399 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--change", "@"]);
400 insta::assert_snapshot!(stdout, @"");
401 insta::assert_snapshot!(stderr, @r###"
402 Creating branch push-yostqsxwqrlt for revision @
403 Branch changes to push to origin:
404 Add branch push-yostqsxwqrlt to 28d7620ea63a
405 "###);
406 // test pushing two changes at once
407 std::fs::write(workspace_root.join("file"), "modified2").unwrap();
408 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "-c=@", "-c=@-"]);
409 insta::assert_snapshot!(stdout, @"");
410 insta::assert_snapshot!(stderr, @r###"
411 Creating branch push-yqosqzytrlsw for revision @-
412 Branch changes to push to origin:
413 Force branch push-yostqsxwqrlt from 28d7620ea63a to 48d8c7948133
414 Add branch push-yqosqzytrlsw to fa16a14170fb
415 "###);
416 // specifying the same change twice doesn't break things
417 std::fs::write(workspace_root.join("file"), "modified3").unwrap();
418 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "-c=@", "-c=@"]);
419 insta::assert_snapshot!(stdout, @"");
420 insta::assert_snapshot!(stderr, @r###"
421 Branch changes to push to origin:
422 Force branch push-yostqsxwqrlt from 48d8c7948133 to b5f030322b1d
423 "###);
424
425 // specifying the same branch with --change/--branch doesn't break things
426 std::fs::write(workspace_root.join("file"), "modified4").unwrap();
427 let (stdout, stderr) = test_env.jj_cmd_ok(
428 &workspace_root,
429 &["git", "push", "-c=@", "-b=push-yostqsxwqrlt"],
430 );
431 insta::assert_snapshot!(stdout, @"");
432 insta::assert_snapshot!(stderr, @r###"
433 Branch changes to push to origin:
434 Force branch push-yostqsxwqrlt from b5f030322b1d to 4df62cec2ee4
435 "###);
436
437 // try again with --change that moves the branch forward
438 std::fs::write(workspace_root.join("file"), "modified5").unwrap();
439 test_env.jj_cmd_ok(
440 &workspace_root,
441 &[
442 "branch",
443 "set",
444 "-r=@-",
445 "--allow-backwards",
446 "push-yostqsxwqrlt",
447 ],
448 );
449 let stdout = test_env.jj_cmd_success(&workspace_root, &["status"]);
450 insta::assert_snapshot!(stdout, @r###"
451 Working copy changes:
452 M file
453 Working copy : yostqsxw 3e2ce808 bar
454 Parent commit: yqosqzyt fa16a141 push-yostqsxwqrlt* push-yqosqzytrlsw | foo
455 "###);
456 let (stdout, stderr) = test_env.jj_cmd_ok(
457 &workspace_root,
458 &["git", "push", "-c=@", "-b=push-yostqsxwqrlt"],
459 );
460 insta::assert_snapshot!(stdout, @"");
461 insta::assert_snapshot!(stderr, @r###"
462 Branch changes to push to origin:
463 Force branch push-yostqsxwqrlt from 4df62cec2ee4 to 3e2ce808759b
464 "###);
465 let stdout = test_env.jj_cmd_success(&workspace_root, &["status"]);
466 insta::assert_snapshot!(stdout, @r###"
467 Working copy changes:
468 M file
469 Working copy : yostqsxw 3e2ce808 push-yostqsxwqrlt | bar
470 Parent commit: yqosqzyt fa16a141 push-yqosqzytrlsw | foo
471 "###);
472
473 // Test changing `git.push-branch-prefix`. It causes us to push again.
474 let (stdout, stderr) = test_env.jj_cmd_ok(
475 &workspace_root,
476 &[
477 "git",
478 "push",
479 "--config-toml",
480 r"git.push-branch-prefix='test-'",
481 "--change=@",
482 ],
483 );
484 insta::assert_snapshot!(stdout, @"");
485 insta::assert_snapshot!(stderr, @r###"
486 Creating branch test-yostqsxwqrlt for revision @
487 Branch changes to push to origin:
488 Add branch test-yostqsxwqrlt to 3e2ce808759b
489 "###);
490}
491
492#[test]
493fn test_git_push_revisions() {
494 let (test_env, workspace_root) = set_up();
495 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "foo"]);
496 std::fs::write(workspace_root.join("file"), "contents").unwrap();
497 test_env.jj_cmd_ok(&workspace_root, &["new", "-m", "bar"]);
498 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "branch-1"]);
499 std::fs::write(workspace_root.join("file"), "modified").unwrap();
500 test_env.jj_cmd_ok(&workspace_root, &["new", "-m", "baz"]);
501 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "branch-2a"]);
502 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "branch-2b"]);
503 std::fs::write(workspace_root.join("file"), "modified again").unwrap();
504
505 // Push an empty set
506 let (_stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "-r=none()"]);
507 insta::assert_snapshot!(stderr, @r###"
508 Warning: No branches point to the specified revisions: none()
509 Nothing changed.
510 "###);
511 // Push a revision with no branches
512 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "-r=@--"]);
513 insta::assert_snapshot!(stdout, @"");
514 insta::assert_snapshot!(stderr, @r###"
515 Warning: No branches point to the specified revisions: @--
516 Nothing changed.
517 "###);
518 // Push a revision with a single branch
519 let (stdout, stderr) =
520 test_env.jj_cmd_ok(&workspace_root, &["git", "push", "-r=@-", "--dry-run"]);
521 insta::assert_snapshot!(stdout, @"");
522 insta::assert_snapshot!(stderr, @r###"
523 Branch changes to push to origin:
524 Add branch branch-1 to 7decc7932d9c
525 Dry-run requested, not pushing.
526 "###);
527 // Push multiple revisions of which some have branches
528 let (stdout, stderr) = test_env.jj_cmd_ok(
529 &workspace_root,
530 &["git", "push", "-r=@--", "-r=@-", "--dry-run"],
531 );
532 insta::assert_snapshot!(stdout, @"");
533 insta::assert_snapshot!(stderr, @r###"
534 Warning: No branches point to the specified revisions: @--
535 Branch changes to push to origin:
536 Add branch branch-1 to 7decc7932d9c
537 Dry-run requested, not pushing.
538 "###);
539 // Push a revision with a multiple branches
540 let (stdout, stderr) =
541 test_env.jj_cmd_ok(&workspace_root, &["git", "push", "-r=@", "--dry-run"]);
542 insta::assert_snapshot!(stdout, @"");
543 insta::assert_snapshot!(stderr, @r###"
544 Branch changes to push to origin:
545 Add branch branch-2a to 1b45449e18d0
546 Add branch branch-2b to 1b45449e18d0
547 Dry-run requested, not pushing.
548 "###);
549 // Repeating a commit doesn't result in repeated messages about the branch
550 let (stdout, stderr) = test_env.jj_cmd_ok(
551 &workspace_root,
552 &["git", "push", "-r=@-", "-r=@-", "--dry-run"],
553 );
554 insta::assert_snapshot!(stdout, @"");
555 insta::assert_snapshot!(stderr, @r###"
556 Branch changes to push to origin:
557 Add branch branch-1 to 7decc7932d9c
558 Dry-run requested, not pushing.
559 "###);
560}
561
562#[test]
563fn test_git_push_mixed() {
564 let (test_env, workspace_root) = set_up();
565 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "foo"]);
566 std::fs::write(workspace_root.join("file"), "contents").unwrap();
567 test_env.jj_cmd_ok(&workspace_root, &["new", "-m", "bar"]);
568 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "branch-1"]);
569 std::fs::write(workspace_root.join("file"), "modified").unwrap();
570 test_env.jj_cmd_ok(&workspace_root, &["new", "-m", "baz"]);
571 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "branch-2a"]);
572 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "branch-2b"]);
573 std::fs::write(workspace_root.join("file"), "modified again").unwrap();
574
575 let (stdout, stderr) = test_env.jj_cmd_ok(
576 &workspace_root,
577 &["git", "push", "--change=@--", "--branch=branch-1", "-r=@"],
578 );
579 insta::assert_snapshot!(stdout, @"");
580 insta::assert_snapshot!(stderr, @r###"
581 Creating branch push-yqosqzytrlsw for revision @--
582 Branch changes to push to origin:
583 Add branch push-yqosqzytrlsw to fa16a14170fb
584 Add branch branch-1 to 7decc7932d9c
585 Add branch branch-2a to 1b45449e18d0
586 Add branch branch-2b to 1b45449e18d0
587 "###);
588}
589
590#[test]
591fn test_git_push_existing_long_branch() {
592 let (test_env, workspace_root) = set_up();
593 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "foo"]);
594 std::fs::write(workspace_root.join("file"), "contents").unwrap();
595 test_env.jj_cmd_ok(
596 &workspace_root,
597 &["branch", "create", "push-19b790168e73f7a73a98deae21e807c0"],
598 );
599
600 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--change=@"]);
601 insta::assert_snapshot!(stdout, @"");
602 insta::assert_snapshot!(stderr, @r###"
603 Branch changes to push to origin:
604 Add branch push-19b790168e73f7a73a98deae21e807c0 to fa16a14170fb
605 "###);
606}
607
608#[test]
609fn test_git_push_unsnapshotted_change() {
610 let (test_env, workspace_root) = set_up();
611 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "foo"]);
612 std::fs::write(workspace_root.join("file"), "contents").unwrap();
613 test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--change", "@"]);
614 std::fs::write(workspace_root.join("file"), "modified").unwrap();
615 test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--change", "@"]);
616}
617
618#[test]
619fn test_git_push_conflict() {
620 let (test_env, workspace_root) = set_up();
621 std::fs::write(workspace_root.join("file"), "first").unwrap();
622 test_env.jj_cmd_ok(&workspace_root, &["commit", "-m", "first"]);
623 std::fs::write(workspace_root.join("file"), "second").unwrap();
624 test_env.jj_cmd_ok(&workspace_root, &["commit", "-m", "second"]);
625 std::fs::write(workspace_root.join("file"), "third").unwrap();
626 test_env.jj_cmd_ok(&workspace_root, &["rebase", "-r", "@", "-d", "@--"]);
627 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "my-branch"]);
628 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "third"]);
629 let stderr = test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--all"]);
630 insta::assert_snapshot!(stderr, @r###"
631 Error: Won't push commit 739c4f08a056 since it has conflicts
632 "###);
633}
634
635#[test]
636fn test_git_push_no_description() {
637 let (test_env, workspace_root) = set_up();
638 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "my-branch"]);
639 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m="]);
640 let stderr =
641 test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--branch", "my-branch"]);
642 insta::assert_snapshot!(stderr, @r###"
643 Error: Won't push commit 5b36783cd11c since it has no description
644 "###);
645}
646
647#[test]
648fn test_git_push_missing_author() {
649 let (test_env, workspace_root) = set_up();
650 let run_without_var = |var: &str, args: &[&str]| {
651 test_env
652 .jj_cmd(&workspace_root, args)
653 .env_remove(var)
654 .assert()
655 .success();
656 };
657 run_without_var("JJ_USER", &["checkout", "root()", "-m=initial"]);
658 run_without_var("JJ_USER", &["branch", "create", "missing-name"]);
659 let stderr = test_env.jj_cmd_failure(
660 &workspace_root,
661 &["git", "push", "--branch", "missing-name"],
662 );
663 insta::assert_snapshot!(stderr, @r###"
664 Error: Won't push commit 944313939bbd since it has no author and/or committer set
665 "###);
666 run_without_var("JJ_EMAIL", &["checkout", "root()", "-m=initial"]);
667 run_without_var("JJ_EMAIL", &["branch", "create", "missing-email"]);
668 let stderr =
669 test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--branch=missing-email"]);
670 insta::assert_snapshot!(stderr, @r###"
671 Error: Won't push commit 59354714f789 since it has no author and/or committer set
672 "###);
673}
674
675#[test]
676fn test_git_push_missing_committer() {
677 let (test_env, workspace_root) = set_up();
678 let run_without_var = |var: &str, args: &[&str]| {
679 test_env
680 .jj_cmd(&workspace_root, args)
681 .env_remove(var)
682 .assert()
683 .success();
684 };
685 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "missing-name"]);
686 run_without_var("JJ_USER", &["describe", "-m=no committer name"]);
687 let stderr =
688 test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--branch=missing-name"]);
689 insta::assert_snapshot!(stderr, @r###"
690 Error: Won't push commit 4fd190283d1a since it has no author and/or committer set
691 "###);
692 test_env.jj_cmd_ok(&workspace_root, &["checkout", "root()"]);
693 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "missing-email"]);
694 run_without_var("JJ_EMAIL", &["describe", "-m=no committer email"]);
695 let stderr =
696 test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--branch=missing-email"]);
697 insta::assert_snapshot!(stderr, @r###"
698 Error: Won't push commit eab97428a6ec since it has no author and/or committer set
699 "###);
700
701 // Test message when there are multiple reasons (missing committer and
702 // description)
703 run_without_var("JJ_EMAIL", &["describe", "-m=", "missing-email"]);
704 let stderr =
705 test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--branch=missing-email"]);
706 insta::assert_snapshot!(stderr, @r###"
707 Error: Won't push commit 1143ed607f54 since it has no description and it has no author and/or committer set
708 "###);
709}
710
711#[test]
712fn test_git_push_deleted() {
713 let (test_env, workspace_root) = set_up();
714
715 test_env.jj_cmd_ok(&workspace_root, &["branch", "delete", "branch1"]);
716 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--deleted"]);
717 insta::assert_snapshot!(stdout, @"");
718 insta::assert_snapshot!(stderr, @r###"
719 Branch changes to push to origin:
720 Delete branch branch1 from 45a3aa29e907
721 "###);
722 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-rall()"]);
723 insta::assert_snapshot!(stdout, @r###"
724 ◉ rlzusymt test.user@example.com 2001-02-03 08:05:10 branch2 8476341e
725 │ (empty) description 2
726 │ ◉ lzmmnrxq test.user@example.com 2001-02-03 08:05:08 45a3aa29
727 ├─╯ (empty) description 1
728 │ @ yqosqzyt test.user@example.com 2001-02-03 08:05:13 5b36783c
729 ├─╯ (empty) (no description set)
730 ◉ zzzzzzzz root() 00000000
731 "###);
732 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--deleted"]);
733 insta::assert_snapshot!(stdout, @"");
734 insta::assert_snapshot!(stderr, @r###"
735 Nothing changed.
736 "###);
737}
738
739#[test]
740fn test_git_push_conflicting_branches() {
741 let (test_env, workspace_root) = set_up();
742 test_env.add_config("git.auto-local-branch = true");
743 let git_repo = {
744 let mut git_repo_path = workspace_root.clone();
745 git_repo_path.extend([".jj", "repo", "store", "git"]);
746 git2::Repository::open(&git_repo_path).unwrap()
747 };
748
749 // Forget remote ref, move local ref, then fetch to create conflict.
750 git_repo
751 .find_reference("refs/remotes/origin/branch2")
752 .unwrap()
753 .delete()
754 .unwrap();
755 test_env.jj_cmd_ok(&workspace_root, &["git", "import"]);
756 test_env.jj_cmd_ok(&workspace_root, &["new", "root()", "-m=description 3"]);
757 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "branch2"]);
758 test_env.jj_cmd_ok(&workspace_root, &["git", "fetch"]);
759 insta::assert_snapshot!(
760 test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]), @r###"
761 branch1: lzmmnrxq 45a3aa29 (empty) description 1
762 @origin: lzmmnrxq 45a3aa29 (empty) description 1
763 branch2 (conflicted):
764 + yostqsxw 8e670e2d (empty) description 3
765 + rlzusymt 8476341e (empty) description 2
766 @origin (behind by 1 commits): rlzusymt 8476341e (empty) description 2
767 "###);
768
769 let bump_branch1 = || {
770 test_env.jj_cmd_ok(&workspace_root, &["new", "branch1", "-m=bump"]);
771 test_env.jj_cmd_ok(&workspace_root, &["branch", "set", "branch1"]);
772 };
773
774 // Conflicting branch at @
775 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
776 insta::assert_snapshot!(stdout, @"");
777 insta::assert_snapshot!(stderr, @r###"
778 Warning: Branch branch2 is conflicted
779 Hint: Run `jj branch list` to inspect, and use `jj branch set` to fix it up.
780 Nothing changed.
781 "###);
782
783 // --branch should be blocked by conflicting branch
784 let stderr = test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--branch", "branch2"]);
785 insta::assert_snapshot!(stderr, @r###"
786 Error: Branch branch2 is conflicted
787 Hint: Run `jj branch list` to inspect, and use `jj branch set` to fix it up.
788 "###);
789
790 // --all shouldn't be blocked by conflicting branch
791 bump_branch1();
792 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--all"]);
793 insta::assert_snapshot!(stdout, @"");
794 insta::assert_snapshot!(stderr, @r###"
795 Warning: Branch branch2 is conflicted
796 Hint: Run `jj branch list` to inspect, and use `jj branch set` to fix it up.
797 Branch changes to push to origin:
798 Move branch branch1 from 45a3aa29e907 to fd1d63e031ea
799 "###);
800
801 // --revisions shouldn't be blocked by conflicting branch
802 bump_branch1();
803 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "-rall()"]);
804 insta::assert_snapshot!(stdout, @"");
805 insta::assert_snapshot!(stderr, @r###"
806 Warning: Branch branch2 is conflicted
807 Hint: Run `jj branch list` to inspect, and use `jj branch set` to fix it up.
808 Branch changes to push to origin:
809 Move branch branch1 from fd1d63e031ea to 8263cf992d33
810 "###);
811}
812
813#[test]
814fn test_git_push_deleted_untracked() {
815 let (test_env, workspace_root) = set_up();
816
817 // Absent local branch shouldn't be considered "deleted" compared to
818 // non-tracking remote branch.
819 test_env.jj_cmd_ok(&workspace_root, &["branch", "delete", "branch1"]);
820 test_env.jj_cmd_ok(&workspace_root, &["branch", "untrack", "branch1@origin"]);
821 let (_stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--deleted"]);
822 insta::assert_snapshot!(stderr, @r###"
823 Nothing changed.
824 "###);
825 let stderr = test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--branch=branch1"]);
826 insta::assert_snapshot!(stderr, @r###"
827 Error: No such branch: branch1
828 "###);
829}
830
831#[test]
832fn test_git_push_tracked_vs_all() {
833 let (test_env, workspace_root) = set_up();
834 test_env.jj_cmd_ok(&workspace_root, &["new", "branch1", "-mmoved branch1"]);
835 test_env.jj_cmd_ok(&workspace_root, &["branch", "set", "branch1"]);
836 test_env.jj_cmd_ok(&workspace_root, &["new", "branch2", "-mmoved branch2"]);
837 test_env.jj_cmd_ok(&workspace_root, &["branch", "delete", "branch2"]);
838 test_env.jj_cmd_ok(&workspace_root, &["branch", "untrack", "branch1@origin"]);
839 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "branch3"]);
840 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]);
841 insta::assert_snapshot!(stdout, @r###"
842 branch1: vruxwmqv a25f24af (empty) moved branch1
843 branch1@origin: lzmmnrxq 45a3aa29 (empty) description 1
844 branch2 (deleted)
845 @origin: rlzusymt 8476341e (empty) description 2
846 (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this)
847 branch3: znkkpsqq 998d6a78 (empty) moved branch2
848 "###);
849
850 // At this point, only branch2 is still tracked. `jj git push --tracked` would
851 // try to push it and no other branches.
852 let (_stdout, stderr) =
853 test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--tracked", "--dry-run"]);
854 insta::assert_snapshot!(stderr, @r###"
855 Branch changes to push to origin:
856 Delete branch branch2 from 8476341eb395
857 Dry-run requested, not pushing.
858 "###);
859
860 // Untrack the last remaining tracked branch.
861 test_env.jj_cmd_ok(&workspace_root, &["branch", "untrack", "branch2@origin"]);
862 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]);
863 insta::assert_snapshot!(stdout, @r###"
864 branch1: vruxwmqv a25f24af (empty) moved branch1
865 branch1@origin: lzmmnrxq 45a3aa29 (empty) description 1
866 branch2@origin: rlzusymt 8476341e (empty) description 2
867 branch3: znkkpsqq 998d6a78 (empty) moved branch2
868 "###);
869
870 // Now, no branches are tracked. --tracked does not push anything
871 let (_stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--tracked"]);
872 insta::assert_snapshot!(stderr, @r###"
873 Nothing changed.
874 "###);
875
876 // All branches are still untracked.
877 // - --all tries to push branch1, but fails because a branch with the same
878 // name exist on the remote.
879 // - --all succeeds in pushing branch3, since there is no branch of the same
880 // name on the remote.
881 // - It does not try to push branch2.
882 //
883 // TODO: Not trying to push branch2 could be considered correct, or perhaps
884 // we want to consider this as a deletion of the branch that failed because
885 // the branch was untracked. In the latter case, an error message should be
886 // printed. Some considerations:
887 // - Whatever we do should be consistent with what `jj branch list` does; it
888 // currently does *not* list branches like branch2 as "about to be deleted",
889 // as can be seen above.
890 // - We could consider showing some hint on `jj branch untrack branch2@origin`
891 // instead of showing an error here.
892 let (_stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--all"]);
893 insta::assert_snapshot!(stderr, @r###"
894 Warning: Non-tracking remote branch branch1@origin exists
895 Hint: Run `jj branch track branch1@origin` to import the remote branch.
896 Branch changes to push to origin:
897 Add branch branch3 to 998d6a7853d9
898 "###);
899}
900
901#[test]
902fn test_git_push_moved_forward_untracked() {
903 let (test_env, workspace_root) = set_up();
904
905 test_env.jj_cmd_ok(&workspace_root, &["new", "branch1", "-mmoved branch1"]);
906 test_env.jj_cmd_ok(&workspace_root, &["branch", "set", "branch1"]);
907 test_env.jj_cmd_ok(&workspace_root, &["branch", "untrack", "branch1@origin"]);
908 let (_stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
909 insta::assert_snapshot!(stderr, @r###"
910 Warning: Non-tracking remote branch branch1@origin exists
911 Hint: Run `jj branch track branch1@origin` to import the remote branch.
912 Nothing changed.
913 "###);
914}
915
916#[test]
917fn test_git_push_moved_sideways_untracked() {
918 let (test_env, workspace_root) = set_up();
919
920 test_env.jj_cmd_ok(&workspace_root, &["new", "root()", "-mmoved branch1"]);
921 test_env.jj_cmd_ok(
922 &workspace_root,
923 &["branch", "set", "--allow-backwards", "branch1"],
924 );
925 test_env.jj_cmd_ok(&workspace_root, &["branch", "untrack", "branch1@origin"]);
926 let (_stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push"]);
927 insta::assert_snapshot!(stderr, @r###"
928 Warning: Non-tracking remote branch branch1@origin exists
929 Hint: Run `jj branch track branch1@origin` to import the remote branch.
930 Nothing changed.
931 "###);
932}
933
934#[test]
935fn test_git_push_to_remote_named_git() {
936 let (test_env, workspace_root) = set_up();
937 let git_repo = {
938 let mut git_repo_path = workspace_root.clone();
939 git_repo_path.extend([".jj", "repo", "store", "git"]);
940 git2::Repository::open(&git_repo_path).unwrap()
941 };
942 git_repo.remote_rename("origin", "git").unwrap();
943
944 let stderr =
945 test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--all", "--remote=git"]);
946 insta::assert_snapshot!(stderr, @r###"
947 Branch changes to push to git:
948 Add branch branch1 to 45a3aa29e907
949 Add branch branch2 to 8476341eb395
950 Error: Git remote named 'git' is reserved for local Git repository
951 "###);
952}