just playing with tangled
at diffedit3 952 lines 40 kB view raw
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}