Files
jj-vcs-jj/cli/tests/test_git_push.rs
Gaëtan Lehmann bd036daa12 change-id: update tests
Note: we use longer short change-IDs in test_abandon_command to avoid
false codebook detection.
2026-06-27 15:29:00 +02:00

2952 lines
103 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2022 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use indoc::indoc;
use testutils::TestResult;
use testutils::git;
use crate::common::CommandOutput;
use crate::common::TestEnvironment;
use crate::common::TestWorkDir;
use crate::common::to_toml_value;
fn git_repo_dir_for_jj_repo(work_dir: &TestWorkDir<'_>) -> std::path::PathBuf {
work_dir
.root()
.join(".jj")
.join("repo")
.join("store")
.join("git")
}
fn set_up(test_env: &TestEnvironment) {
test_env.run_jj_in(".", ["git", "init", "origin"]).success();
let origin_dir = test_env.work_dir("origin");
let origin_git_repo_path = git_repo_dir_for_jj_repo(&origin_dir);
origin_dir
.run_jj(["describe", "-m=description 1"])
.success();
origin_dir
.run_jj(["bookmark", "create", "-r@", "bookmark1"])
.success();
origin_dir
.run_jj(["new", "root()", "-m=description 2"])
.success();
origin_dir
.run_jj(["bookmark", "create", "-r@", "bookmark2"])
.success();
origin_dir.run_jj(["git", "export"]).success();
test_env
.run_jj_in(
".",
[
"git",
"clone",
"--config=remotes.origin.auto-track-bookmarks='*'",
origin_git_repo_path.to_str().unwrap(),
"local",
],
)
.success();
}
#[test]
fn test_git_push_nothing() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// Show the setup. `insta` has trouble if this is done inside `set_up()`
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: qpvuntsm 9b2e76de (empty) description 1
@origin: qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@origin: psuskuln 7ac95915 (empty) description 2
[EOF]
");
// No bookmarks to push yet
let output = work_dir.run_jj(["git", "push", "--all"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Nothing changed.
[EOF]
");
// Tracked bookmark is up to date
work_dir
.run_jj(["bookmark", "track", "bookmark1@origin"])
.success();
let output = work_dir.run_jj(["git", "push", "--tracked"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_default_remote_selection() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// add a second remote (actually the same)
let other_remote_path = test_env
.env_root()
.join("origin")
.join(".jj")
.join("repo")
.join("store")
.join("git");
work_dir
.run_jj([
"git",
"remote",
"add",
"other",
other_remote_path.to_str().unwrap(),
])
.success();
// select remote based on git.push config
let output = work_dir.run_jj(["git", "push", "--config=git.push=other", "-b", "bookmark1"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Refusing to create new remote bookmark bookmark1@other
Hint: Run `jj bookmark track bookmark1 --remote=other` and try again.
[EOF]
[exit status: 1]
");
// remove origin, should select other (with hint)
work_dir
.run_jj(["git", "remote", "remove", "origin"])
.success();
let output = work_dir.run_jj(["git", "push", "-b", "bookmark1"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Hint: Pushing to the only existing remote: other
Changes to push to other:
bookmark: bookmark1 [add to 9b2e76de3920]
[EOF]
");
}
#[test]
fn test_git_push_current_bookmark() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("local");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
// Update some bookmarks. `bookmark1` is not a current bookmark, but
// `bookmark2` and `my-bookmark` are.
work_dir
.run_jj(["describe", "bookmark1", "-m", "modified bookmark1 commit"])
.success();
work_dir.run_jj(["new", "bookmark2"]).success();
work_dir
.run_jj(["bookmark", "set", "bookmark2", "-r@"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "my-bookmark"])
.success();
work_dir.run_jj(["describe", "-m", "foo"]).success();
// Check the setup
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: qpvuntsm e5ce6d9a (empty) modified bookmark1 commit
@origin (ahead by 1 commits, behind by 1 commits): qpvuntsm/1 9b2e76de (hidden) (empty) description 1
bookmark2: rostqsxw b1fc3a6f (empty) foo
@origin (behind by 1 commits): psuskuln 7ac95915 (empty) description 2
my-bookmark: rostqsxw b1fc3a6f (empty) foo
@origin (not created yet)
[EOF]
");
// First dry-run. `bookmark1` should not get pushed.
let output = work_dir.run_jj(["git", "push", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark2 [move forward from 7ac95915695d to b1fc3a6f6cd9]
bookmark: my-bookmark [add to b1fc3a6f6cd9]
Dry-run requested, not pushing.
[EOF]
");
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark2 [move forward from 7ac95915695d to b1fc3a6f6cd9]
bookmark: my-bookmark [add to b1fc3a6f6cd9]
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: qpvuntsm e5ce6d9a (empty) modified bookmark1 commit
@origin (ahead by 1 commits, behind by 1 commits): qpvuntsm/1 9b2e76de (hidden) (empty) description 1
bookmark2: rostqsxw b1fc3a6f (empty) foo
@origin: rostqsxw b1fc3a6f (empty) foo
my-bookmark: rostqsxw b1fc3a6f (empty) foo
@origin: rostqsxw b1fc3a6f (empty) foo
[EOF]
");
// Try pushing backwards
work_dir
.run_jj([
"bookmark",
"set",
"bookmark2",
"-rbookmark2-",
"--allow-backwards",
])
.success();
// This behavior is a strangeness of our definition of the default push revset.
// We could consider changing it.
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No bookmarks/tags found in the default push revset: remote_bookmarks(remote=origin)..@
Nothing changed.
[EOF]
");
// We can move a bookmark backwards
let output = work_dir.run_jj(["git", "push", "-bbookmark2"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark2 [move backward from b1fc3a6f6cd9 to 7ac95915695d]
[EOF]
");
}
#[test]
fn test_git_push_parent_bookmark() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
work_dir.run_jj(["edit", "bookmark1"]).success();
work_dir
.run_jj(["describe", "-m", "modified bookmark1 commit"])
.success();
work_dir
.run_jj(["new", "-m", "non-empty description"])
.success();
work_dir.write_file("file", "file");
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [move sideways from 9b2e76de3920 to 80560a3e08e2]
[EOF]
");
}
#[test]
fn test_git_push_tag_in_default_target() {
let test_env = TestEnvironment::default();
test_env
.run_jj_in(".", ["git", "init", "--colocate", "origin"])
.success();
let origin_dir = test_env.work_dir("origin");
origin_dir.run_jj(["commit", "-morigin"]).success();
origin_dir.run_jj(["tag", "set", "-r@-", "tag1"]).success();
test_env
.run_jj_in(".", ["git", "clone", "--tag=*", "origin", "local"])
.success();
let work_dir = test_env.work_dir("local");
work_dir.run_jj(["commit", "-mlocal"]).success();
work_dir
.run_jj(["tag", "set", "--allow-move", "-r@-", "tag1"])
.success();
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
tag: tag1 [move sideways from 110db8edfa5f to b60842ac7691]
[EOF]
");
}
#[test]
fn test_git_push_no_matching_bookmark() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
work_dir.run_jj(["new"]).success();
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No bookmarks/tags found in the default push revset: remote_bookmarks(remote=origin)..@
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_matching_bookmark_unchanged() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
work_dir.run_jj(["new", "bookmark1"]).success();
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No bookmarks/tags found in the default push revset: remote_bookmarks(remote=origin)..@
Nothing changed.
[EOF]
");
}
/// Test that `jj git push` without arguments pushes a bookmark to the specified
/// remote even if it's already up to date on another remote
/// (`remote_bookmarks(remote=<remote>)..@` vs. `remote_bookmarks()..@`).
#[test]
fn test_git_push_other_remote_has_bookmark() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
// Create another remote (but actually the same)
let other_remote_path = test_env
.env_root()
.join("origin")
.join(".jj")
.join("repo")
.join("store")
.join("git");
work_dir
.run_jj([
"git",
"remote",
"add",
"other",
other_remote_path.to_str().unwrap(),
])
.success();
// Modify bookmark1 and push it to `origin`
work_dir.run_jj(["edit", "bookmark1"]).success();
work_dir.run_jj(["describe", "-m=modified"]).success();
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [move sideways from 9b2e76de3920 to a843bfad2abb]
[EOF]
");
// Since it's already pushed to origin, nothing will happen if push again
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No bookmarks/tags found in the default push revset: remote_bookmarks(remote=origin)..@
Nothing changed.
[EOF]
");
// The bookmark was moved on the "other" remote as well (since it's actually the
// same remote), but `jj` is not aware of that since it thinks this is a
// different remote. So, the push should fail.
//
// But it succeeds! That's because the bookmark is created at the same location
// as it is on the remote. This would also work for a descendant.
//
// TODO: Saner test?
work_dir
.run_jj(["bookmark", "track", "bookmark1", "--remote=other"])
.success();
let output = work_dir.run_jj(["git", "push", "--remote=other"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to other:
bookmark: bookmark1 [add to a843bfad2abb]
[EOF]
");
}
#[test]
fn test_git_push_forward_unexpectedly_moved() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// Move bookmark1 forward on the remote
let origin_dir = test_env.work_dir("origin");
origin_dir
.run_jj(["new", "bookmark1", "-m=remote"])
.success();
origin_dir.write_file("remote", "remote");
origin_dir
.run_jj(["bookmark", "set", "bookmark1", "-r@"])
.success();
origin_dir.run_jj(["git", "export"]).success();
// Move bookmark1 forward to another commit locally
work_dir.run_jj(["new", "bookmark1", "-m=local"]).success();
work_dir.write_file("local", "local");
work_dir
.run_jj(["bookmark", "set", "bookmark1", "-r@"])
.success();
// Pushing should fail
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [move forward from 9b2e76de3920 to 624f94a35f00]
Warning: The following references unexpectedly moved on the remote:
refs/heads/bookmark1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
// The ref name should be colorized
let output = work_dir.run_jj(["git", "push", "--color=always"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [move forward from 9b2e76de3920 to 624f94a35f00]
Warning: The following references unexpectedly moved on the remote:
refs/heads/bookmark1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_sideways_unexpectedly_moved() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// Move bookmark1 forward on the remote
let origin_dir = test_env.work_dir("origin");
origin_dir
.run_jj(["new", "bookmark1", "-m=remote"])
.success();
origin_dir.write_file("remote", "remote");
origin_dir
.run_jj(["bookmark", "set", "bookmark1", "-r@"])
.success();
insta::assert_snapshot!(get_bookmark_output(&origin_dir), @"
bookmark1: uruxwmqv 9ab45f12 remote
@git (behind by 1 commits): qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@git: psuskuln 7ac95915 (empty) description 2
[EOF]
");
origin_dir.run_jj(["git", "export"]).success();
// Move bookmark1 sideways to another commit locally
work_dir.run_jj(["new", "root()", "-m=local"]).success();
work_dir.write_file("local", "local");
work_dir
.run_jj(["bookmark", "set", "bookmark1", "--allow-backwards", "-r@"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: wmkuslsw db695c28 local
@origin (ahead by 1 commits, behind by 1 commits): qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@origin: psuskuln 7ac95915 (empty) description 2
[EOF]
");
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [move sideways from 9b2e76de3920 to db695c2833a1]
Warning: The following references unexpectedly moved on the remote:
refs/heads/bookmark1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
// The ref name should be colorized
let output = work_dir.run_jj(["git", "push", "--color=always"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [move sideways from 9b2e76de3920 to db695c2833a1]
Warning: The following references unexpectedly moved on the remote:
refs/heads/bookmark1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
}
// This tests whether the push checks that the remote bookmarks are in expected
// positions.
#[test]
fn test_git_push_deletion_unexpectedly_moved() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// Move bookmark1 forward on the remote
let origin_dir = test_env.work_dir("origin");
origin_dir
.run_jj(["new", "bookmark1", "-m=remote"])
.success();
origin_dir.write_file("remote", "remote");
origin_dir
.run_jj(["bookmark", "set", "bookmark1", "-r@"])
.success();
insta::assert_snapshot!(get_bookmark_output(&origin_dir), @"
bookmark1: uruxwmqv 9ab45f12 remote
@git (behind by 1 commits): qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@git: psuskuln 7ac95915 (empty) description 2
[EOF]
");
origin_dir.run_jj(["git", "export"]).success();
// Delete bookmark1 locally
work_dir
.run_jj(["bookmark", "delete", "bookmark1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1 (deleted)
@origin: qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@origin: psuskuln 7ac95915 (empty) description 2
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--bookmark", "bookmark1"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [delete from 9b2e76de3920]
Warning: The following references unexpectedly moved on the remote:
refs/heads/bookmark1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_unexpectedly_deleted() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// Delete bookmark1 forward on the remote
let origin_dir = test_env.work_dir("origin");
origin_dir
.run_jj(["bookmark", "delete", "bookmark1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&origin_dir), @"
bookmark1 (deleted)
@git: qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@git: psuskuln 7ac95915 (empty) description 2
[EOF]
");
origin_dir.run_jj(["git", "export"]).success();
// Move bookmark1 sideways to another commit locally
work_dir.run_jj(["new", "root()", "-m=local"]).success();
work_dir.write_file("local", "local");
work_dir
.run_jj(["bookmark", "set", "bookmark1", "--allow-backwards", "-r@"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: kpqxywon 09919fb0 local
@origin (ahead by 1 commits, behind by 1 commits): qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@origin: psuskuln 7ac95915 (empty) description 2
[EOF]
");
// Pushing a moved bookmark fails if deleted on remote
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [move sideways from 9b2e76de3920 to 09919fb051bf]
Warning: The following references unexpectedly moved on the remote:
refs/heads/bookmark1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
work_dir
.run_jj(["bookmark", "delete", "bookmark1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1 (deleted)
@origin: qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@origin: psuskuln 7ac95915 (empty) description 2
[EOF]
");
// git does not allow to push a deleted bookmark if we expect it to exist even
// though it was already deleted
let output = work_dir.run_jj(["git", "push", "-bbookmark1"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [delete from 9b2e76de3920]
Warning: The following references unexpectedly moved on the remote:
refs/heads/bookmark1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_creation_unexpectedly_already_exists() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("local");
// Forget bookmark1 locally
work_dir
.run_jj(["bookmark", "forget", "--include-remotes", "bookmark1"])
.success();
// Create a new bookmark1
work_dir
.run_jj(["new", "root()", "-m=new bookmark1"])
.success();
work_dir.write_file("local", "local");
work_dir
.run_jj(["bookmark", "create", "-r@", "bookmark1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: rostqsxw 132d1b1b new bookmark1
@origin (not created yet)
bookmark2: psuskuln 7ac95915 (empty) description 2
@origin: psuskuln 7ac95915 (empty) description 2
[EOF]
");
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [add to 132d1b1b45fd]
Warning: The following references unexpectedly moved on the remote:
refs/heads/bookmark1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_locally_created_and_rewritten() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// Ensure that remote bookmarks aren't tracked automatically
test_env.add_config("remotes.origin.auto-track-bookmarks = '~*'");
// Push locally-created bookmark
work_dir.run_jj(["new", "root()", "-mlocal 1"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "my"])
.success();
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Refusing to create new remote bookmark my@origin
Hint: Run `jj bookmark track my --remote=origin` and try again.
Nothing changed.
[EOF]
");
// Absent-tracked bookmark can be pushed
work_dir
.run_jj(["bookmark", "track", "my", "--remote=origin"])
.success();
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: my [add to 2901efb0a58e]
[EOF]
");
// Rewrite it and push again, which would fail if the pushed bookmark weren't
// set to "tracking"
work_dir.run_jj(["describe", "-mlocal 2"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: qpvuntsm 9b2e76de (empty) description 1
@origin: qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@origin: psuskuln 7ac95915 (empty) description 2
my: truxwmqv 9de3639a (empty) local 2
@origin (ahead by 1 commits, behind by 1 commits): truxwmqv/1 2901efb0 (hidden) (empty) local 1
[EOF]
");
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: my [move sideways from 2901efb0a58e to 9de3639a5039]
[EOF]
");
}
#[test]
fn test_git_push_multiple() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.add_config("revset-aliases.'immutable_heads()' = 'none()'");
let origin_dir = test_env.work_dir("origin");
let work_dir = test_env.work_dir("local");
// Add tracked tag
origin_dir
.run_jj(["tag", "set", "tag1", "-rbookmark1"])
.success();
origin_dir.run_jj(["git", "export"]).success();
work_dir.run_jj(["git", "fetch", "--tag=*"]).success();
// Update bookmarks and tags
work_dir
.run_jj(["bookmark", "delete", "bookmark1"])
.success();
work_dir
.run_jj(["bookmark", "set", "--allow-backwards", "bookmark2", "-r@"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "my-bookmark"])
.success();
work_dir.run_jj(["describe", "-m", "foo"]).success();
work_dir
.run_jj(["tag", "set", "--allow-move", "tag1", "-r@"])
.success();
work_dir.run_jj(["tag", "set", "tag2", "-r@"]).success();
// Make conflicting changes on bookmark3
work_dir
.run_jj(["bookmark", "set", "bookmark3", "-rbookmark2"])
.success();
origin_dir
.run_jj(["bookmark", "set", "bookmark3", "-rbookmark1"])
.success();
origin_dir.run_jj(["git", "export"]).success();
// Check the setup
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1 (deleted)
@origin: qpvuntsm 9b2e76de (empty) description 1
bookmark2: yqosqzyt 0cb91ecd (empty) foo
@origin (ahead by 1 commits, behind by 1 commits): psuskuln 7ac95915 (empty) description 2
bookmark3: yqosqzyt 0cb91ecd (empty) foo
@origin (not created yet)
my-bookmark: yqosqzyt 0cb91ecd (empty) foo
@origin (not created yet)
[EOF]
");
insta::assert_snapshot!(get_tag_output(&work_dir), @"
tag1: yqosqzyt 0cb91ecd (empty) foo
@origin (ahead by 1 commits, behind by 1 commits): qpvuntsm 9b2e76de (empty) description 1
tag2: yqosqzyt 0cb91ecd (empty) foo
[EOF]
");
// First dry-run
// TODO: untracked tags should be included when gets stabilized (#7528)
let output = work_dir.run_jj(["git", "push", "--all", "--deleted", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Refusing to create new remote tag tag2@origin
Changes to push to origin:
bookmark: bookmark1 [delete from 9b2e76de3920]
bookmark: bookmark2 [move sideways from 7ac95915695d to 0cb91ecd4965]
bookmark: bookmark3 [add to 0cb91ecd4965]
bookmark: my-bookmark [add to 0cb91ecd4965]
tag: tag1 [move sideways from 9b2e76de3920 to 0cb91ecd4965]
Dry-run requested, not pushing.
[EOF]
");
// Dry run requesting two specific bookmarks and one tag
let output = work_dir.run_jj([
"git",
"push",
"--bookmark=bookmark1|my-bookmark",
"--tag=tag2",
"--dry-run",
]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [delete from 9b2e76de3920]
bookmark: my-bookmark [add to 0cb91ecd4965]
tag: tag2 [add to 0cb91ecd4965]
Dry-run requested, not pushing.
[EOF]
");
// Dry run requesting two specific bookmarks twice
let output = work_dir.run_jj([
"git",
"push",
"-b=bookmark1",
"-b=my-bookmark",
"-b=bookmark1",
"-b=my-*",
"--dry-run",
]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [delete from 9b2e76de3920]
bookmark: my-bookmark [add to 0cb91ecd4965]
Dry-run requested, not pushing.
[EOF]
");
// Dry run with glob pattern
let output = work_dir.run_jj(["git", "push", "-b='bookmark?'", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [delete from 9b2e76de3920]
bookmark: bookmark2 [move sideways from 7ac95915695d to 0cb91ecd4965]
bookmark: bookmark3 [add to 0cb91ecd4965]
Dry-run requested, not pushing.
[EOF]
");
// Unmatched bookmark/tag name
let output = work_dir.run_jj(["git", "push", "--bookmark=foo", "--tag=bar"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No matching bookmarks for names: foo
Warning: No matching tags for names: bar
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["git", "push", "-b=foo", "-b='?bookmark'"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No matching bookmarks for names: foo
Nothing changed.
[EOF]
");
// --deleted is required to push deleted bookmarks even with --all
let output = work_dir.run_jj(["git", "push", "--all", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Refusing to push deleted bookmark bookmark1
Hint: Push deleted bookmarks with --deleted or forget the bookmark to suppress this warning.
Warning: Refusing to create new remote tag tag2@origin
Changes to push to origin:
bookmark: bookmark2 [move sideways from 7ac95915695d to 0cb91ecd4965]
bookmark: bookmark3 [add to 0cb91ecd4965]
bookmark: my-bookmark [add to 0cb91ecd4965]
tag: tag1 [move sideways from 9b2e76de3920 to 0cb91ecd4965]
Dry-run requested, not pushing.
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--all", "--deleted", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Refusing to create new remote tag tag2@origin
Changes to push to origin:
bookmark: bookmark1 [delete from 9b2e76de3920]
bookmark: bookmark2 [move sideways from 7ac95915695d to 0cb91ecd4965]
bookmark: bookmark3 [add to 0cb91ecd4965]
bookmark: my-bookmark [add to 0cb91ecd4965]
tag: tag1 [move sideways from 9b2e76de3920 to 0cb91ecd4965]
Dry-run requested, not pushing.
[EOF]
");
// All pushed bookmarks/tags should be committed to the operation.
// Therefore, the subsequent "git import" should be noop.
let output = work_dir.run_jj(["git", "push", "--all", "--deleted"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Refusing to create new remote tag tag2@origin
Changes to push to origin:
bookmark: bookmark1 [delete from 9b2e76de3920]
bookmark: bookmark2 [move sideways from 7ac95915695d to 0cb91ecd4965]
bookmark: bookmark3 [add to 0cb91ecd4965]
bookmark: my-bookmark [add to 0cb91ecd4965]
tag: tag1 [move sideways from 9b2e76de3920 to 0cb91ecd4965]
Warning: The following references unexpectedly moved on the remote:
refs/heads/bookmark3 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["git", "import"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark2: yqosqzyt 0cb91ecd (empty) foo
@origin: yqosqzyt 0cb91ecd (empty) foo
bookmark3: yqosqzyt 0cb91ecd (empty) foo
@origin (not created yet)
my-bookmark: yqosqzyt 0cb91ecd (empty) foo
@origin: yqosqzyt 0cb91ecd (empty) foo
[EOF]
");
insta::assert_snapshot!(get_tag_output(&work_dir), @"
tag1: yqosqzyt 0cb91ecd (empty) foo
@origin: yqosqzyt 0cb91ecd (empty) foo
tag2: yqosqzyt 0cb91ecd (empty) foo
[EOF]
");
let output = work_dir.run_jj(["log", "-rall()"]);
insta::assert_snapshot!(output, @"
@ yqosqzyt test.user@example.com 2001-02-03 08:05:20 bookmark2 bookmark3* my-bookmark tag1 tag2 0cb91ecd
│ (empty) foo
│ ○ psuskuln test.user@example.com 2001-02-03 08:05:10 7ac95915
├─╯ (empty) description 2
│ ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 9b2e76de
├─╯ (empty) description 1
◆ zzzzzzzz root() 00000000
[EOF]
");
// Specified branch is up to date
let output = work_dir.run_jj(["git", "push", "--branch", "bookmark2"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Bookmark bookmark2@origin already matches bookmark2
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_changes() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
work_dir.run_jj(["describe", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir.run_jj(["new", "-m", "bar"]).success();
work_dir.write_file("file", "modified");
let output = work_dir.run_jj(["git", "push", "--change", "@"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Creating bookmark push-rostqsxwqrlt for revision rostqsxwqrlt
Changes to push to origin:
bookmark: push-rostqsxwqrlt [add to bf0ba55ca017]
[EOF]
");
// specified bookmark is up to date
let output = work_dir.run_jj(["git", "push", "--change", "@"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Bookmark push-rostqsxwqrlt@origin already matches push-rostqsxwqrlt
Nothing changed.
[EOF]
");
// test pushing two changes at once
work_dir.write_file("file", "modified2");
let output = work_dir.run_jj(["git", "push", "-c=(@|@-)"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Creating bookmark push-yqosqzytrlsw for revision yqosqzytrlsw
Changes to push to origin:
bookmark: push-rostqsxwqrlt [move sideways from bf0ba55ca017 to a8cf969f69c3]
bookmark: push-yqosqzytrlsw [add to 0f8164cd580b]
[EOF]
");
// specifying the same change twice doesn't break things
work_dir.write_file("file", "modified3");
let output = work_dir.run_jj(["git", "push", "-c=(@|@)"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: push-rostqsxwqrlt [move sideways from a8cf969f69c3 to 141495e3e47f]
[EOF]
");
// specifying the same bookmark with --change/--bookmark doesn't break things
work_dir.write_file("file", "modified4");
let output = work_dir.run_jj(["git", "push", "-c=@", "-b=push-rostqsxwqrlt"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: push-rostqsxwqrlt [move sideways from 141495e3e47f to c5c0eb67f8fc]
[EOF]
");
// try again with --change that could move the bookmark forward
work_dir.write_file("file", "modified5");
work_dir
.run_jj([
"bookmark",
"set",
"-r=@-",
"--allow-backwards",
"push-rostqsxwqrlt",
])
.success();
let output = work_dir.run_jj(["status"]);
insta::assert_snapshot!(output, @"
Working copy changes:
M file
Working copy (@) : rostqsxw cabf33c8 bar
Parent commit (@-): yqosqzyt 0f8164cd push-rostqsxwqrlt* push-yqosqzytrlsw | foo
[EOF]
");
let output = work_dir.run_jj(["git", "push", "-c=@", "-b=push-rostqsxwqrlt"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Bookmark already exists: push-rostqsxwqrlt
Hint: Use 'jj bookmark move' to move it, and 'jj git push -b push-rostqsxwqrlt' to push it
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["status"]);
insta::assert_snapshot!(output, @"
Working copy changes:
M file
Working copy (@) : rostqsxw cabf33c8 bar
Parent commit (@-): yqosqzyt 0f8164cd push-rostqsxwqrlt* push-yqosqzytrlsw | foo
[EOF]
");
// Test changing `git_push_bookmark` template. It causes us to push again.
let output = work_dir.run_jj([
"git",
"push",
&format!(
"--config=templates.git_push_bookmark={}",
to_toml_value("'test-' ++ change_id.short()")
),
"--change=@",
]);
insta::assert_snapshot!(output, @"
------- stderr -------
Creating bookmark test-rostqsxwqrlt for revision rostqsxwqrlt
Changes to push to origin:
bookmark: test-rostqsxwqrlt [add to cabf33c82fed]
[EOF]
");
// Generating duplicate bookmarks should not be allowed
let output = work_dir.run_jj([
"git",
"push",
"--config=templates.git_push_bookmark=\"'dupe-bookmark'\"",
"--change=(@|root()+)",
]);
insta::assert_snapshot!(output, @"
------- stderr -------
Creating bookmark dupe-bookmark for revision rostqsxwqrlt
Error: Bookmark already exists: dupe-bookmark
Hint: Use 'jj bookmark move' to move it, and 'jj git push -b dupe-bookmark' to push it
[EOF]
[exit status: 1]
");
// Bad `git_push_bookmark` templates
let output = work_dir.run_jj([
"git",
"push",
r#"--config=templates.git_push_bookmark='""'"#,
"--change=@",
]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Empty bookmark name generated
[EOF]
[exit status: 1]
");
// Generate a bookmark name with non-utf8 content
work_dir.write_file("file", vec![b'\xff']);
let output = work_dir.run_jj([
"git",
"push",
"--config=templates.git_push_bookmark=self.diff().git()",
"--change=@",
]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Invalid character in bookmark name
Caused by: invalid utf-8 sequence of 1 bytes from index 138
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_changes_with_name() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
work_dir.run_jj(["describe", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir.run_jj(["new", "-m", "pushed"]).success();
work_dir.write_file("file", "modified");
// Normal behavior.
let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: b1 [add to 89ea5ca5f04c]
[EOF]
");
// Spaces before the = sign are treated like part of the bookmark name and such
// bookmarks cannot be pushed.
let output = work_dir.run_jj(["git", "push", "--named", "b1 = @"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Could not parse 'b1 ' as a bookmark name
Caused by:
1: Failed to parse bookmark name: Syntax error
2: --> 1:3
|
1 | b1
| ^---
|
= expected <EOI>
Hint: For example, `--named myfeature=@` is valid syntax
[EOF]
[exit status: 2]
");
// test pushing a change with an empty name
let output = work_dir.run_jj(["git", "push", "--named", "=@"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Argument '=@' must have the form NAME=REVISION, with both NAME and REVISION non-empty
Hint: For example, `--named myfeature=@` is valid syntax
[EOF]
[exit status: 2]
");
// Unparsable name
let output = work_dir.run_jj(["git", "push", "--named", ":!:=@"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Could not parse ':!:' as a bookmark name
Caused by:
1: Failed to parse bookmark name: Syntax error
2: --> 1:1
|
1 | :!:
| ^---
|
= expected <identifier>, <string_literal>, or <raw_string_literal>
Hint: For example, `--named myfeature=@` is valid syntax
[EOF]
[exit status: 2]
");
// test pushing a change with an empty revision
let output = work_dir.run_jj(["git", "push", "--named", "b2="]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Argument 'b2=' must have the form NAME=REVISION, with both NAME and REVISION non-empty
Hint: For example, `--named myfeature=@` is valid syntax
[EOF]
[exit status: 2]
");
// test pushing a change with no equals sign
let output = work_dir.run_jj(["git", "push", "--named", "b2"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Argument 'b2' must include '=' and have the form NAME=REVISION
Hint: For example, `--named myfeature=@` is valid syntax
[EOF]
[exit status: 2]
");
// test pushing the same change with the same name again
let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Bookmark already exists: b1
Hint: Use 'jj bookmark move' to move it, and 'jj git push -b b1' to push it
[EOF]
[exit status: 1]
");
// test pushing two changes at once
work_dir.write_file("file", "modified2");
let output = work_dir.run_jj(["git", "push", "--named=b2=(@|@-)"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Revset `(@|@-)` resolved to more than one revision
Hint: The revset `(@|@-)` resolved to these revisions:
rostqsxw 34bd1e21 b1* | pushed
yqosqzyt 0f8164cd foo
[EOF]
[exit status: 1]
");
// specifying the same bookmark with --named/--bookmark
work_dir.write_file("file", "modified4");
let output = work_dir.run_jj(["git", "push", "--named=b2=@", "-b=b2"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: b2 [add to 5317293e9031]
[EOF]
");
}
#[test]
fn test_git_push_changes_with_name_deleted_tracked() {
let test_env = TestEnvironment::default();
set_up(&test_env);
// Unset immutable_heads so that untracking branches does not move the working
// copy
test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
let work_dir = test_env.work_dir("local");
// Create a second empty remote `another_remote`
test_env
.run_jj_in(".", ["git", "init", "another_remote"])
.success();
let another_remote_git_repo_path =
git_repo_dir_for_jj_repo(&test_env.work_dir("another_remote"));
work_dir
.run_jj([
"git",
"remote",
"add",
"another_remote",
another_remote_git_repo_path.to_str().unwrap(),
])
.success();
work_dir.run_jj(["describe", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir.run_jj(["new", "-m", "pushed"]).success();
work_dir.write_file("file", "modified");
// Normal push as part of the test setup
let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: b1 [add to 08f401c17d51]
[EOF]
");
work_dir.run_jj(["bookmark", "delete", "b1"]).success();
// Test the setup
let output = work_dir
.run_jj(["bookmark", "list", "--all", "b1"])
.success();
insta::assert_snapshot!(output, @"
b1 (deleted)
@origin: kpqxywon 08f401c1 pushed
[EOF]
------- stderr -------
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.
[EOF]
");
// Can't push `b1` with --named to the same or another remote if it's deleted
// locally and still tracked on `origin`
let output = work_dir.run_jj(["git", "push", "--named", "b1=@", "--remote=another_remote"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Tracked remote bookmarks exist for deleted bookmark: b1
Hint: Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark untrack b1` to disassociate them.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["git", "push", "--named", "b1=@", "--remote=origin"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Tracked remote bookmarks exist for deleted bookmark: b1
Hint: Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark untrack b1` to disassociate them.
[EOF]
[exit status: 1]
");
// OK to push to a different remote once the bookmark is no longer tracked on
// `origin`
work_dir
.run_jj(["bookmark", "untrack", "b1", "--remote=origin"])
.success();
let output = work_dir
.run_jj(["bookmark", "list", "--all", "b1"])
.success();
insta::assert_snapshot!(output, @"
b1@origin: kpqxywon 08f401c1 pushed
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--named", "b1=@", "--remote=another_remote"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to another_remote:
bookmark: b1 [add to 08f401c17d51]
[EOF]
");
let output = work_dir
.run_jj(["bookmark", "list", "--all", "b1"])
.success();
insta::assert_snapshot!(output, @"
b1: kpqxywon 08f401c1 pushed
@another_remote: kpqxywon 08f401c1 pushed
b1@origin: kpqxywon 08f401c1 pushed
[EOF]
");
}
#[test]
fn test_git_push_changes_with_name_untracked_or_forgotten() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// Unset immutable_heads so that untracking branches does not move the working
// copy
test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
work_dir.run_jj(["describe", "-m", "parent"]).success();
work_dir.run_jj(["new", "-m", "pushed_to_remote"]).success();
work_dir.write_file("file", "contents");
work_dir
.run_jj(["new", "-m", "child", "--no-edit"])
.success();
work_dir.write_file("file", "modified");
// Push a branch to a remote, but forget the local branch
work_dir
.run_jj(["git", "push", "--named", "b1=@"])
.success();
work_dir.run_jj(["bookmark", "untrack", "b1"]).success();
work_dir.run_jj(["bookmark", "delete", "b1"]).success();
let output = work_dir
.run_jj(&[
"log",
"-r=::@+",
r#"-T=separate(" ", commit_id.shortest(3), bookmarks, description)"#,
])
.success();
insta::assert_snapshot!(output, @"
○ 366 child
@ 9e0 b1@origin pushed_to_remote
○ aa9 parent
◆ 000
[EOF]
");
let output = work_dir
.run_jj(["bookmark", "list", "--all", "b1"])
.success();
insta::assert_snapshot!(output, @"
b1@origin: rostqsxw 9e0f2255 pushed_to_remote
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Non-tracking remote bookmark b1@origin exists
Hint: Run `jj bookmark track b1 --remote=origin` to import the remote bookmark.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["git", "push", "--named", "b1=@+"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Non-tracking remote bookmark b1@origin exists
Hint: Run `jj bookmark track b1 --remote=origin` to import the remote bookmark.
[EOF]
[exit status: 1]
");
// The bookmarked is still pushed to the remote, but let's entirely forget
// it. In other words, let's forget the remote-tracking bookmarks.
work_dir
.run_jj(&["bookmark", "forget", "b1", "--include-remotes"])
.success();
let output = work_dir
.run_jj(["bookmark", "list", "--all", "b1"])
.success();
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No matching bookmarks for names: b1
[EOF]
");
// Make sure push still errors if we try to push a bookmark with the same name
// to a different location.
let output = work_dir.run_jj(["git", "push", "--named", "b1=@-"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: b1 [add to aa9ad64cb4ce]
Warning: The following references unexpectedly moved on the remote:
refs/heads/b1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
// The bookmark is still forgotten
let output = work_dir.run_jj(["bookmark", "list", "--all", "b1"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No matching bookmarks for names: b1
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--named", "b1=@+"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: b1 [add to 3668cedee47a]
Warning: The following references unexpectedly moved on the remote:
refs/heads/b1 (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
// In this case, pushing the bookmark to the same location where it already is
// succeeds. TODO: This seems pretty safe, but perhaps it should still show
// an error or some sort of warning?
let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: b1 [add to 9e0f22551b20]
[EOF]
");
}
#[test]
fn test_git_push_revisions() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.add_config("revset-aliases.'immutable_heads()' = 'none()'");
let origin_dir = test_env.work_dir("origin");
let work_dir = test_env.work_dir("local");
// Add tracked tags
origin_dir
.run_jj(["tag", "set", "tag-1", "tag-2", "tag-3", "-rbookmark1"])
.success();
origin_dir.run_jj(["git", "export"]).success();
work_dir.run_jj(["git", "fetch", "--tag=*"]).success();
// Update bookmarks and tags
work_dir.run_jj(["describe", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir.run_jj(["new", "-m", "bar"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bookmark-1"])
.success();
work_dir.write_file("file", "modified");
work_dir.run_jj(["new", "-m", "baz"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bookmark-2a"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bookmark-2b"])
.success();
work_dir.write_file("file", "modified again");
work_dir
.run_jj(["tag", "set", "--allow-move", "-r@", "tag-2"])
.success();
work_dir
.run_jj(["new", "root()", "--no-edit", "-mnew3"])
.success();
work_dir
.run_jj(["tag", "set", "--allow-move", "-rsubject(new3)", "tag-3"])
.success();
insta::assert_snapshot!(work_dir.run_jj(["log"]), @"
@ mylxulpl test.user@example.com 2001-02-03 08:05:23 bookmark-2a* bookmark-2b* tag-2* 11142838
│ baz
○ wmkuslsw test.user@example.com 2001-02-03 08:05:20 bookmark-1* 2b2c6c07
│ bar
○ yqosqzyt test.user@example.com 2001-02-03 08:05:18 33b167f8
│ foo
│ ○ lznxytkn test.user@example.com 2001-02-03 08:05:24 tag-3* 44ea9da4
├─╯ (empty) new3
│ ○ psuskuln test.user@example.com 2001-02-03 08:05:10 bookmark2 7ac95915
├─╯ (empty) description 2
│ ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 bookmark1 tag-1 tag-2@origin tag-3@origin 9b2e76de
├─╯ (empty) description 1
◆ zzzzzzzz root() 00000000
[EOF]
");
// Bookmark/tag at revision is up to date
let output = work_dir.run_jj(["git", "push", "--revisions", "bookmark1"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Nothing changed.
[EOF]
");
// Push an empty set
let output = work_dir.run_jj(["git", "push", "-r=none()"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No bookmarks/tags point to the specified revisions: none()
Nothing changed.
[EOF]
");
// Push a revision with no bookmarks/tags
let output = work_dir.run_jj(["git", "push", "-r=@--"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No bookmarks/tags point to the specified revisions: @--
Nothing changed.
[EOF]
");
// Push a revision with a single bookmark
let output = work_dir.run_jj(["git", "push", "-r=@-", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark-1 [add to 2b2c6c07cdf3]
Dry-run requested, not pushing.
[EOF]
");
// Push a revision with a single tag
let output = work_dir.run_jj(["git", "push", "-r=subject(new3)", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
tag: tag-3 [move sideways from 9b2e76de3920 to 44ea9da46261]
Dry-run requested, not pushing.
[EOF]
");
// Push multiple revisions of which some have bookmarks
let output = work_dir.run_jj(["git", "push", "-r=@--", "-r=@-", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No bookmarks/tags point to the specified revisions: @--
Changes to push to origin:
bookmark: bookmark-1 [add to 2b2c6c07cdf3]
Dry-run requested, not pushing.
[EOF]
");
// Push a revision with a multiple bookmarks and tags
let output = work_dir.run_jj(["git", "push", "-r=@", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark-2a [add to 111428387a8b]
bookmark: bookmark-2b [add to 111428387a8b]
tag: tag-2 [move sideways from 9b2e76de3920 to 111428387a8b]
Dry-run requested, not pushing.
[EOF]
");
// Repeating a commit doesn't result in repeated messages about the bookmark
let output = work_dir.run_jj(["git", "push", "-r=@-", "-r=@-", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark-1 [add to 2b2c6c07cdf3]
Dry-run requested, not pushing.
[EOF]
");
// Repetition via name and commit doesn't result in repeated messages either
let output = work_dir.run_jj(["git", "push", "-r=@-", "-b", "bookmark-1", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark-1 [add to 2b2c6c07cdf3]
Dry-run requested, not pushing.
[EOF]
");
}
#[test]
fn test_git_push_mixed() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("revset-aliases.'immutable_heads()' = 'none()'");
let work_dir = test_env.work_dir("local");
work_dir.run_jj(["describe", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir.run_jj(["new", "-m", "bar"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bookmark-1"])
.success();
work_dir.run_jj(["tag", "set", "-r@", "tag-1"]).success();
work_dir.write_file("file", "modified");
work_dir.run_jj(["new", "-m", "baz"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bookmark-2a"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bookmark-2b"])
.success();
work_dir.write_file("file", "modified again");
// New bookmark shouldn't be pushed by -r=..
let output = work_dir.run_jj([
"git",
"push",
"--change=@--",
"--bookmark=bookmark-1",
"--tag=tag-1",
"-r=@",
]);
insta::assert_snapshot!(output, @"
------- stderr -------
Creating bookmark push-yqosqzytrlsw for revision yqosqzytrlsw
Warning: Refusing to create new remote bookmark bookmark-2a@origin
Hint: Run `jj bookmark track bookmark-2a --remote=origin` and try again.
Warning: Refusing to create new remote bookmark bookmark-2b@origin
Hint: Run `jj bookmark track bookmark-2b --remote=origin` and try again.
Changes to push to origin:
bookmark: push-yqosqzytrlsw [add to 0f8164cd580b]
bookmark: bookmark-1 [add to 27bd1dbb5995]
tag: tag-1 [add to 78f71024a5c3]
[EOF]
");
}
#[test]
fn test_git_push_bookmarks_and_tags_of_same_name() {
let test_env = TestEnvironment::default();
test_env
.run_jj_in(".", ["git", "init", "--colocate", "origin"])
.success();
let origin_dir = test_env.work_dir("origin");
origin_dir.run_jj(["describe", "-morigin"]).success();
origin_dir.run_jj(["new"]).success();
origin_dir
.run_jj(["bookmark", "set", "-r@-", "foo", "bar"])
.success();
origin_dir
.run_jj(["tag", "set", "-r@-", "foo", "bar"])
.success();
test_env
.run_jj_in(
".",
["git", "clone", "--branch=*", "--tag=*", "origin", "local"],
)
.success();
let work_dir = test_env.work_dir("local");
work_dir.run_jj(["bookmark", "track", "*"]).success();
// Move bookmarks and tags
work_dir.run_jj(["new", "foo", "-mlocal"]).success();
work_dir.run_jj(["new"]).success();
work_dir
.run_jj(["bookmark", "set", "-r@-", "foo", "bar"])
.success();
work_dir
.run_jj(["tag", "set", "-r@-", "--allow-move", "foo", "bar"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bar: truxwmqv 0163b9ed (empty) local
@origin (behind by 1 commits): qpvuntsm 110db8ed (empty) origin
foo: truxwmqv 0163b9ed (empty) local
@origin (behind by 1 commits): qpvuntsm 110db8ed (empty) origin
[EOF]
");
insta::assert_snapshot!(get_tag_output(&work_dir), @"
bar: truxwmqv 0163b9ed (empty) local
@origin (behind by 1 commits): qpvuntsm 110db8ed (empty) origin
foo: truxwmqv 0163b9ed (empty) local
@origin (behind by 1 commits): qpvuntsm 110db8ed (empty) origin
[EOF]
");
// Both bookmark and tag of the same name can be selected
let output = work_dir.run_jj(["git", "push", "--dry-run", "--bookmark=foo", "--tag=foo"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: foo [move forward from 110db8edfa5f to 0163b9ed8732]
tag: foo [move forward from 110db8edfa5f to 0163b9ed8732]
Dry-run requested, not pushing.
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--dry-run", "-r@-"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bar [move forward from 110db8edfa5f to 0163b9ed8732]
bookmark: foo [move forward from 110db8edfa5f to 0163b9ed8732]
tag: bar [move forward from 110db8edfa5f to 0163b9ed8732]
tag: foo [move forward from 110db8edfa5f to 0163b9ed8732]
Dry-run requested, not pushing.
[EOF]
");
// Bookmark and tag can be selected individually
let output = work_dir.run_jj(["git", "push", "--dry-run", "--bookmark=foo", "--tag=bar"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: foo [move forward from 110db8edfa5f to 0163b9ed8732]
tag: bar [move forward from 110db8edfa5f to 0163b9ed8732]
Dry-run requested, not pushing.
[EOF]
");
}
#[test]
fn test_git_push_allow_new_heuristics() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
test_env
.run_jj_in(".", ["git", "init", "--colocate", "upstream"])
.success();
work_dir
.run_jj([
"git",
"remote",
"add",
"upstream",
test_env
.work_dir("upstream")
.root()
.join(".git")
.to_str()
.unwrap(),
])
.success();
work_dir.run_jj(["describe", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir
.run_jj(["bookmark", "create", "untracked"])
.success();
work_dir.run_jj(["tag", "set", "untracked"]).success();
let start_op = work_dir.current_operation_id();
// Pushing untracked refs with --bookmark/--tag automatically tracks it.
let output = work_dir.run_jj(["git", "push", "--bookmark=untracked", "--tag=untracked"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: untracked [add to b9124726209b]
tag: untracked [add to b9124726209b]
[EOF]
");
work_dir.run_jj(["op", "restore", &start_op]).success();
work_dir
.run_jj([
"git",
"push",
"--bookmark=untracked",
"--tag=untracked",
"--remote=upstream",
])
.success();
// ...unless the ref is already tracked with a different remote, in
// which case the user may have simply forgotten to specify --remote.
let output = work_dir.run_jj(["git", "push", "--bookmark=untracked"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Refusing to create new remote bookmark untracked@origin
Hint: Run `jj bookmark track untracked --remote=origin` and try again.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["git", "push", "--tag=untracked"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Refusing to create new remote tag untracked@origin
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_unsnapshotted_change() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
work_dir.run_jj(["describe", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir.run_jj(["git", "push", "--change", "@"]).success();
work_dir.write_file("file", "modified");
work_dir.run_jj(["git", "push", "--change", "@"]).success();
}
#[test]
fn test_git_push_conflict() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
work_dir.write_file("file", "first");
work_dir.run_jj(["commit", "-m", "first"]).success();
work_dir.write_file("file", "second");
work_dir.run_jj(["commit", "-m", "second"]).success();
work_dir.write_file("file", "third");
work_dir
.run_jj(["rebase", "-r", "@", "-o", "@--"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "my-bookmark"])
.success();
work_dir.run_jj(["describe", "-m", "third"]).success();
let output = work_dir.run_jj(["git", "push", "--all"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Won't push bookmark my-bookmark: commit b96eaa9bb3d8 has conflicts
yostqsxw b96eaa9b my-bookmark | (conflict) third
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_no_description() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("local");
work_dir
.run_jj(["bookmark", "create", "-r@", "my-bookmark"])
.success();
work_dir.run_jj(["describe", "-m="]).success();
let output = work_dir.run_jj(["git", "push", "--bookmark", "my-bookmark"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Won't push commit 8d23abddc924 since it has no description
Hint: Rejected commit: yqosqzyt 8d23abdd my-bookmark* | (empty) (no description set)
[EOF]
[exit status: 1]
");
work_dir
.run_jj([
"git",
"push",
"--bookmark",
"my-bookmark",
"--allow-empty-description",
])
.success();
}
#[test]
fn test_git_push_no_description_in_immutable() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("local");
work_dir
.run_jj(["bookmark", "create", "-r@", "imm"])
.success();
work_dir.run_jj(["describe", "-m="]).success();
work_dir.run_jj(["new", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir
.run_jj(["bookmark", "create", "-r@", "my-bookmark"])
.success();
let output = work_dir.run_jj(["git", "push", "--bookmark=my-bookmark", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Won't push commit 8d23abddc924 since it has no description
Hint: Rejected commit: yqosqzyt 8d23abdd imm* | (empty) (no description set)
[EOF]
[exit status: 1]
");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "imm""#);
let output = work_dir.run_jj(["git", "push", "--bookmark=my-bookmark", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: my-bookmark [add to aed7f818ab04]
Dry-run requested, not pushing.
[EOF]
");
}
#[test]
fn test_git_push_missing_author() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("local");
let run_without_var = |var: &str, args: &[&str]| {
work_dir
.run_jj_with(|cmd| cmd.args(args).env_remove(var))
.success();
};
run_without_var("JJ_USER", &["new", "root()", "-m=initial"]);
run_without_var("JJ_USER", &["bookmark", "create", "-r@", "missing-name"]);
let output = work_dir.run_jj(["git", "push", "--bookmark", "missing-name"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Won't push commit 14c3571c4153 since it has no author and/or committer set
Hint: Rejected commit: truxwmqv 14c3571c missing-name* | (empty) initial
[EOF]
[exit status: 1]
");
run_without_var("JJ_EMAIL", &["new", "root()", "-m=initial"]);
run_without_var("JJ_EMAIL", &["bookmark", "create", "-r@", "missing-email"]);
let output = work_dir.run_jj(["git", "push", "--bookmark=missing-email"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Won't push commit bb4ea60fc9ba since it has no author and/or committer set
Hint: Rejected commit: kpqxywon bb4ea60f missing-email* | (empty) initial
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_missing_author_in_immutable() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("local");
let run_without_var = |var: &str, args: &[&str]| {
work_dir
.run_jj_with(|cmd| cmd.args(args).env_remove(var))
.success();
};
run_without_var("JJ_USER", &["new", "root()", "-m=no author name"]);
run_without_var("JJ_EMAIL", &["new", "-m=no author email"]);
work_dir
.run_jj(["bookmark", "create", "-r@", "imm"])
.success();
work_dir.run_jj(["new", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir
.run_jj(["bookmark", "create", "-r@", "my-bookmark"])
.success();
let output = work_dir.run_jj(["git", "push", "--bookmark=my-bookmark", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Won't push commit 0f6d496a6e33 since it has no author and/or committer set
Hint: Rejected commit: rostqsxw 0f6d496a imm* | (empty) no author email
[EOF]
[exit status: 1]
");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "imm""#);
let output = work_dir.run_jj(["git", "push", "--bookmark=my-bookmark", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: my-bookmark [add to 1e056846da20]
Dry-run requested, not pushing.
[EOF]
");
}
#[test]
fn test_git_push_missing_committer() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("local");
let run_without_var = |var: &str, args: &[&str]| {
work_dir
.run_jj_with(|cmd| cmd.args(args).env_remove(var))
.success();
};
work_dir
.run_jj(["bookmark", "create", "-r@", "missing-name"])
.success();
run_without_var("JJ_USER", &["describe", "-m=no committer name"]);
let output = work_dir.run_jj(["git", "push", "--bookmark=missing-name"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Won't push commit e8a77cb24da9 since it has no author and/or committer set
Hint: Rejected commit: yqosqzyt e8a77cb2 missing-name* | (empty) no committer name
[EOF]
[exit status: 1]
");
work_dir.run_jj(["new", "root()"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "missing-email"])
.success();
run_without_var("JJ_EMAIL", &["describe", "-m=no committer email"]);
let output = work_dir.run_jj(["git", "push", "--bookmark=missing-email"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Won't push commit 971c50fd8d1d since it has no author and/or committer set
Hint: Rejected commit: kpqxywon 971c50fd missing-email* | (empty) no committer email
[EOF]
[exit status: 1]
");
// Test message when there are multiple reasons (missing committer and
// description)
run_without_var("JJ_EMAIL", &["describe", "-m=", "missing-email"]);
let output = work_dir.run_jj(["git", "push", "--bookmark=missing-email"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Won't push commit 4bd3b55c7759 since it has no description and has no author and/or committer set
Hint: Rejected commit: kpqxywon 4bd3b55c missing-email* | (empty) (no description set)
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_missing_committer_in_immutable() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("local");
let run_without_var = |var: &str, args: &[&str]| {
work_dir
.run_jj_with(|cmd| cmd.args(args).env_remove(var))
.success();
};
run_without_var("JJ_USER", &["describe", "-m=no committer name"]);
work_dir.run_jj(["new"]).success();
run_without_var("JJ_EMAIL", &["describe", "-m=no committer email"]);
work_dir
.run_jj(["bookmark", "create", "-r@", "imm"])
.success();
work_dir.run_jj(["new", "-m", "foo"]).success();
work_dir.write_file("file", "contents");
work_dir
.run_jj(["bookmark", "create", "-r@", "my-bookmark"])
.success();
let output = work_dir.run_jj(["git", "push", "--bookmark=my-bookmark", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Won't push commit 8356575dda50 since it has no author and/or committer set
Hint: Rejected commit: rostqsxw 8356575d imm* | (empty) no committer email
[EOF]
[exit status: 1]
");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "imm""#);
let output = work_dir.run_jj(["git", "push", "--bookmark=my-bookmark", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: my-bookmark [add to 274d0f582429]
Dry-run requested, not pushing.
[EOF]
");
}
#[test]
fn test_git_push_deleted() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let origin_dir = test_env.work_dir("origin");
let work_dir = test_env.work_dir("local");
// Add tracked tag
origin_dir
.run_jj(["tag", "set", "tag1", "-rbookmark1"])
.success();
origin_dir.run_jj(["git", "export"]).success();
work_dir.run_jj(["git", "fetch", "--tag=*"]).success();
work_dir
.run_jj(["bookmark", "delete", "bookmark1"])
.success();
work_dir.run_jj(["tag", "delete", "tag1"]).success();
let output = work_dir.run_jj(["git", "push", "--deleted"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [delete from 9b2e76de3920]
tag: tag1 [delete from 9b2e76de3920]
[EOF]
");
let output = work_dir.run_jj(["log", "-rall()"]);
insta::assert_snapshot!(output, @"
@ yqosqzyt test.user@example.com 2001-02-03 08:05:13 8d23abdd
│ (empty) (no description set)
│ ○ psuskuln test.user@example.com 2001-02-03 08:05:10 bookmark2 7ac95915
├─╯ (empty) description 2
│ ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 9b2e76de
├─╯ (empty) description 1
◆ zzzzzzzz root() 00000000
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--deleted"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_conflicting_bookmarks() -> TestResult {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let git_repo = {
let mut git_repo_path = work_dir.root().to_owned();
git_repo_path.extend([".jj", "repo", "store", "git"]);
git::open(&git_repo_path)
};
// Forget remote ref, move local ref, then fetch to create conflict.
git_repo
.find_reference("refs/remotes/origin/bookmark2")?
.delete()?;
work_dir.run_jj(["git", "import"]).success();
work_dir
.run_jj(["new", "root()", "-m=description 3"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bookmark2"])
.success();
work_dir.run_jj(["git", "fetch"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: qpvuntsm 9b2e76de (empty) description 1
@origin: qpvuntsm 9b2e76de (empty) description 1
bookmark2 (conflicted):
+ postqsxw 602aa604 (empty) description 3
+ psuskuln 7ac95915 (empty) description 2
@origin (behind by 1 commits): psuskuln 7ac95915 (empty) description 2
[EOF]
");
let bump_bookmark1 = || {
work_dir.run_jj(["new", "bookmark1", "-m=bump"]).success();
work_dir
.run_jj(["bookmark", "set", "bookmark1", "-r@"])
.success();
};
// Conflicting bookmark at @
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Bookmark bookmark2 is conflicted
Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up.
Nothing changed.
[EOF]
");
// --bookmark should be blocked by conflicting bookmark
let output = work_dir.run_jj(["git", "push", "--bookmark", "bookmark2"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Error: Bookmark bookmark2 is conflicted
Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up.
[EOF]
[exit status: 1]
");
// --all shouldn't be blocked by conflicting bookmark
bump_bookmark1();
let output = work_dir.run_jj(["git", "push", "--all"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Bookmark bookmark2 is conflicted
Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up.
Changes to push to origin:
bookmark: bookmark1 [move forward from 9b2e76de3920 to 2eb9ca9d923e]
[EOF]
");
// --revisions shouldn't be blocked by conflicting bookmark
bump_bookmark1();
let output = work_dir.run_jj(["git", "push", "-rall()"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Bookmark bookmark2 is conflicted
Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up.
Changes to push to origin:
bookmark: bookmark1 [move forward from 2eb9ca9d923e to 7b78a524086f]
[EOF]
");
Ok(())
}
#[test]
fn test_git_push_deleted_untracked() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// Absent local bookmark shouldn't be considered "deleted" compared to
// non-tracking remote bookmark.
// TODO: include tag if we add "tag untrack" command
work_dir
.run_jj(["bookmark", "delete", "bookmark1"])
.success();
work_dir
.run_jj(["bookmark", "untrack", "bookmark1"])
.success();
let output = work_dir.run_jj(["git", "push", "--deleted"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--bookmark=bookmark1"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: No matching bookmarks for names: bookmark1
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_deleted_remote_conflict() {
let test_env = TestEnvironment::default();
// create origin
test_env.run_jj_in(".", ["git", "init", "origin"]).success();
let origin_dir = test_env.work_dir("origin");
let origin_git_repo_path = git_repo_dir_for_jj_repo(&origin_dir);
origin_dir
.run_jj(["describe", "-m=description 1"])
.success();
origin_dir
.run_jj(["bookmark", "create", "-r@", "my-bookmark"])
.success();
origin_dir.run_jj(["git", "export"]).success();
// clone without colocation
test_env
.run_jj_in(
".",
[
"git",
"clone",
"--no-colocate",
"--config=remotes.origin.auto-track-bookmarks='*'",
origin_git_repo_path.to_str().unwrap(),
"local",
],
)
.success();
let work_dir = test_env.work_dir("local");
// advance the origin, fetch it
let origin_dir = test_env.work_dir("origin");
origin_dir.run_jj(["new", "-m", "description 2"]).success();
origin_dir
.run_jj(["bookmark", "move", "my-bookmark", "--to", "@"])
.success();
origin_dir.run_jj(["git", "export"]).success();
work_dir.run_jj(["git", "fetch"]).success();
// adjust the origin again, fetch concurrently
let origin_dir = test_env.work_dir("origin");
origin_dir
.run_jj(["new", "root()", "-m", "description 3"])
.success();
origin_dir
.run_jj([
"bookmark",
"move",
"my-bookmark",
"--to",
"@",
"--allow-backwards",
])
.success();
origin_dir.run_jj(["git", "export"]).success();
work_dir.run_jj(["git", "fetch", "--at-op=@-"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
my-bookmark (conflicted):
- qpvuntsm/0 9b2e76de (hidden) (empty) description 1
+ poyxmykx fe007030 (empty) description 2
+ onkkpsqq 110b71ca (empty) description 3
@origin (ahead by 2 commits, behind by 1 commits) (conflicted):
- qpvuntsm/0 9b2e76de (hidden) (empty) description 1
+ poyxmykx/1 a37f5af9 (hidden) (empty) description 2
+ onkkpsqq 110b71ca (empty) description 3
[EOF]
");
// fix the local conflict, unpushable remote conflict remains
work_dir
.run_jj(["bookmark", "delete", "my-bookmark"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
my-bookmark (deleted)
@origin (conflicted):
- qpvuntsm/0 9b2e76de (hidden) (empty) description 1
+ poyxmykx/1 a37f5af9 (hidden) (empty) description 2
+ onkkpsqq 110b71ca (empty) description 3
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--deleted"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Bookmark my-bookmark@origin is conflicted
Hint: Run `jj git fetch` to update the conflicted remote bookmark.
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_tracked_vs_all() {
let test_env = TestEnvironment::default();
set_up(&test_env);
test_env.add_config("revset-aliases.'immutable_heads()' = 'none()'");
let origin_dir = test_env.work_dir("origin");
let work_dir = test_env.work_dir("local");
// Add tracked tag
origin_dir
.run_jj(["tag", "set", "tag1", "-rbookmark1"])
.success();
origin_dir
.run_jj(["tag", "set", "tag2", "-rbookmark2"])
.success();
origin_dir.run_jj(["git", "export"]).success();
work_dir.run_jj(["git", "fetch", "--tag=*"]).success();
// Update bookmarks and tags
work_dir
.run_jj(["new", "bookmark1", "-mmoved bookmark1"])
.success();
work_dir
.run_jj(["bookmark", "set", "bookmark1", "-r@"])
.success();
work_dir
.run_jj(["new", "bookmark2", "-mmoved bookmark2"])
.success();
work_dir
.run_jj(["bookmark", "delete", "bookmark2"])
.success();
work_dir.run_jj(["tag", "delete", "tag2"]).success();
work_dir
.run_jj(["bookmark", "untrack", "bookmark1"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bookmark3"])
.success();
work_dir.run_jj(["tag", "set", "-r@", "tag3"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: wmkuslsw 36714d46 (empty) moved bookmark1
bookmark1@origin: qpvuntsm 9b2e76de (empty) description 1
bookmark2 (deleted)
@origin: psuskuln 7ac95915 (empty) description 2
bookmark3: mylxulpl 844b3f91 (empty) moved bookmark2
[EOF]
");
insta::assert_snapshot!(get_tag_output(&work_dir), @"
tag1: qpvuntsm 9b2e76de (empty) description 1
@origin: qpvuntsm 9b2e76de (empty) description 1
tag2 (deleted)
@origin: psuskuln 7ac95915 (empty) description 2
tag3: mylxulpl 844b3f91 (empty) moved bookmark2
[EOF]
");
// At this point, only bookmark2 and tags are still tracked.
// `jj git push --tracked --deleted` would try to push them and no other
// bookmarks nor tags.
let output = work_dir.run_jj(["git", "push", "--tracked", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Refusing to push deleted bookmark bookmark2
Hint: Push deleted bookmarks with --deleted or forget the bookmark to suppress this warning.
Warning: Refusing to push deleted tag tag2
Hint: Push deleted tags with --deleted.
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["git", "push", "--tracked", "--deleted", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark2 [delete from 7ac95915695d]
tag: tag2 [delete from 7ac95915695d]
Dry-run requested, not pushing.
[EOF]
");
// Untrack the last remaining tracked bookmark.
work_dir
.run_jj(["bookmark", "untrack", "bookmark2"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: wmkuslsw 36714d46 (empty) moved bookmark1
bookmark1@origin: qpvuntsm 9b2e76de (empty) description 1
bookmark2@origin: psuskuln 7ac95915 (empty) description 2
bookmark3: mylxulpl 844b3f91 (empty) moved bookmark2
[EOF]
");
// Now, no bookmarks are tracked. --tracked would push tags only
let output = work_dir.run_jj(["git", "push", "--tracked"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Refusing to push deleted tag tag2
Hint: Push deleted tags with --deleted.
Nothing changed.
[EOF]
");
// All bookmarks are still untracked.
// - --all tries to push bookmark1, but fails because a bookmark with the same
// name exist on the remote.
// - --all succeeds in pushing bookmark3, since there is no bookmark of the same
// name on the remote.
// - It does not try to push bookmark2.
//
// TODO: Not trying to push bookmark2 could be considered correct, or perhaps
// we want to consider this as a deletion of the bookmark that failed because
// the bookmark was untracked. In the latter case, an error message should be
// printed. Some considerations:
// - Whatever we do should be consistent with what `jj bookmark list` does; it
// currently does *not* list bookmarks like bookmark2 as "about to be
// deleted", as can be seen above.
// - We could consider showing some hint on `jj bookmark untrack bookmark2
// --remote=origin` instead of showing an error here.
let output = work_dir.run_jj(["git", "push", "--all"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Non-tracking remote bookmark bookmark1@origin exists
Hint: Run `jj bookmark track bookmark1 --remote=origin` to import the remote bookmark.
Warning: Refusing to push deleted tag tag2
Hint: Push deleted tags with --deleted.
Warning: Refusing to create new remote tag tag3@origin
Changes to push to origin:
bookmark: bookmark3 [add to 844b3f914c63]
[EOF]
");
}
#[test]
fn test_git_push_moved_forward_untracked() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
work_dir
.run_jj(["new", "bookmark1", "-mmoved bookmark1"])
.success();
work_dir
.run_jj(["bookmark", "set", "bookmark1", "-r@"])
.success();
work_dir
.run_jj(["bookmark", "untrack", "bookmark1"])
.success();
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Non-tracking remote bookmark bookmark1@origin exists
Hint: Run `jj bookmark track bookmark1 --remote=origin` to import the remote bookmark.
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_moved_sideways_untracked() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
work_dir
.run_jj(["new", "root()", "-mmoved bookmark1"])
.success();
work_dir
.run_jj(["bookmark", "set", "--allow-backwards", "bookmark1", "-r@"])
.success();
work_dir
.run_jj(["bookmark", "untrack", "bookmark1"])
.success();
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Non-tracking remote bookmark bookmark1@origin exists
Hint: Run `jj bookmark track bookmark1 --remote=origin` to import the remote bookmark.
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_to_remote_named_git() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
let git_repo_path = {
let mut git_repo_path = work_dir.root().to_owned();
git_repo_path.extend([".jj", "repo", "store", "git"]);
git_repo_path
};
git::rename_remote(&git_repo_path, "origin", "git");
let output = work_dir.run_jj(["git", "push", "--all", "--remote=git"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to git:
bookmark: bookmark1 [add to 9b2e76de3920]
bookmark: bookmark2 [add to 7ac95915695d]
Error: Git remote named 'git' is reserved for local Git repository
Hint: Run `jj git remote rename` to give a different name.
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_to_remote_with_slashes() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
let git_repo_path = {
let mut git_repo_path = work_dir.root().to_owned();
git_repo_path.extend([".jj", "repo", "store", "git"]);
git_repo_path
};
git::rename_remote(&git_repo_path, "origin", "slash/origin");
let output = work_dir.run_jj(["git", "push", "--all", "--remote=slash/origin"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to slash/origin:
bookmark: bookmark1 [add to 9b2e76de3920]
bookmark: bookmark2 [add to 7ac95915695d]
Error: Git remotes with slashes are incompatible with jj: slash/origin
Hint: Run `jj git remote rename` to give a different name.
[EOF]
[exit status: 1]
");
}
#[test]
fn test_git_push_commits_not_ready() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
work_dir
.run_jj(["new", "bookmark2", "-m", "commit not suitable"])
.success();
work_dir
.run_jj(["bookmark", "set", "bookmark2", "-r@"])
.success();
// ready to push
let output = work_dir.run_jj(["git", "push", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark2 [move forward from 7ac95915695d to e1f429e5205e]
Dry-run requested, not pushing.
[EOF]
");
// rejected with empty username
work_dir
.run_jj_with(|cmd| {
cmd.args(["metaedit", "--update-author"])
.env_remove("JJ_USER")
})
.success();
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Won't push bookmark bookmark2: commit 744d067f0b2d has no author and/or committer set
truxwmqv 744d067f bookmark2* | (empty) commit not suitable
Nothing changed.
[EOF]
");
// rejected for both private content and empty username
let output = work_dir.run_jj(["git", "push", "--config", "git.private-commits=all()"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Won't push bookmark bookmark2: commit 744d067f0b2d has no author and/or committer set and is private
truxwmqv 744d067f bookmark2* | (empty) commit not suitable
Hint: Configured git.private-commits: 'all()'
Nothing changed.
[EOF]
");
// still rejected even with --allow-private
let output = work_dir.run_jj([
"git",
"push",
"--config",
"git.private-commits=all()",
"--allow-private",
]);
insta::assert_snapshot!(output, @"
------- stderr -------
Warning: Won't push bookmark bookmark2: commit 744d067f0b2d has no author and/or committer set
truxwmqv 744d067f bookmark2* | (empty) commit not suitable
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_push_sign_on_push() {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
let template = r#"
separate("\n",
description.first_line(),
if(signature,
separate(", ",
"Signature: " ++ signature.display(),
"Status: " ++ signature.status(),
"Key: " ++ signature.key(),
)
)
)
"#;
work_dir
.run_jj(["new", "bookmark2", "-m", "commit to be signed 1"])
.success();
work_dir
.run_jj(["new", "-m", "commit to be signed 2"])
.success();
work_dir
.run_jj(["bookmark", "set", "bookmark2", "-r@"])
.success();
work_dir
.run_jj(["new", "-m", "commit which should not be signed 1"])
.success();
work_dir
.run_jj(["new", "-m", "commit which should not be signed 2"])
.success();
// There should be no signed commits initially
let output = work_dir.run_jj(["log", "-T", template]);
insta::assert_snapshot!(output, @"
@ commit which should not be signed 2
○ commit which should not be signed 1
○ commit to be signed 2
○ commit to be signed 1
○ description 2
│ ○ description 1
├─╯
[EOF]
");
test_env.add_config(
r#"
signing.backend = "test"
signing.key = "impeccable"
git.sign-on-push = true
"#,
);
let output = work_dir.run_jj(["git", "push", "--dry-run"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark2 [move forward from 7ac95915695d to ab23dfb46418]
Dry-run requested, not pushing.
[EOF]
");
// There should be no signed commits after performing a dry run
let output = work_dir.run_jj(["log", "-T", template]);
insta::assert_snapshot!(output, @"
@ commit which should not be signed 2
○ commit which should not be signed 1
○ commit to be signed 2
○ commit to be signed 1
○ description 2
│ ○ description 1
├─╯
[EOF]
");
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Updated signatures of 2 commits
Rebased 2 descendant commits
Changes to push to origin:
bookmark: bookmark2 [move forward from 7ac95915695d to adc71fb1a763]
Working copy (@) now at: xmkuslsw f36ecefd (empty) commit which should not be signed 2
Parent commit (@-) : kpqxywon a7a9ae6e (empty) commit which should not be signed 1
[EOF]
");
// Only commits which are being pushed should be signed
let output = work_dir.run_jj(["log", "-T", template]);
insta::assert_snapshot!(output, @"
@ commit which should not be signed 2
○ commit which should not be signed 1
○ commit to be signed 2
│ Signature: test-display, Status: good, Key: impeccable
○ commit to be signed 1
│ Signature: test-display, Status: good, Key: impeccable
○ description 2
│ ○ description 1
├─╯
[EOF]
");
// Immutable commits should not be signed
let output = work_dir.run_jj(["bookmark", "move", "bookmark2", "--to", "@-"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Moved 1 bookmarks to kpqxywon a7a9ae6e bookmark2* | (empty) commit which should not be signed 1
[EOF]
");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "bookmark2""#);
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark2 [move forward from adc71fb1a763 to a7a9ae6eb373]
[EOF]
");
let output = work_dir.run_jj(["log", "-T", template, "-r", "::"]);
insta::assert_snapshot!(output, @"
@ commit which should not be signed 2
◆ commit which should not be signed 1
◆ commit to be signed 2
│ Signature: test-display, Status: good, Key: impeccable
◆ commit to be signed 1
│ Signature: test-display, Status: good, Key: impeccable
◆ description 2
│ ○ description 1
├─╯
[EOF]
");
// Do not sign commits from other authors or re-sign unnecessarily
work_dir
.run_jj([
"metaedit",
"--author",
"Test User <someone.else@example.com>",
])
.success();
work_dir
.run_jj(["new", "-B@", "--no-edit", "-m", "pre-signed commit"])
.success();
work_dir.run_jj(["sign", "-r", "@-"]).success();
work_dir
.run_jj(["new", "-m", "commit to be signed 3"])
.success();
work_dir
.run_jj(["bookmark", "set", "bookmark1", "-r@", "--allow-backwards"])
.success();
let output = work_dir.run_jj(["log", "-T", template]);
insta::assert_snapshot!(output, @"
@ commit to be signed 3
○ commit which should not be signed 2
○ pre-signed commit
│ Signature: test-display, Status: good, Key: impeccable
◆ commit which should not be signed 1
~ (elided revisions)
│ ○ description 1
├─╯
[EOF]
");
let output = work_dir.run_jj(["git", "push"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Updated signatures of 1 commits
Changes to push to origin:
bookmark: bookmark1 [move sideways from 9b2e76de3920 to 0f0dc241548c]
Working copy (@) now at: mzsxstzt 0f0dc241 bookmark1 | (empty) commit to be signed 3
Parent commit (@-) : xmkuslsw da79958d (empty) commit which should not be signed 2
[EOF]
");
let output = work_dir.run_jj(["log", "-T", template]);
insta::assert_snapshot!(output, @"
@ commit to be signed 3
│ Signature: test-display, Status: good, Key: impeccable
○ commit which should not be signed 2
○ pre-signed commit
│ Signature: test-display, Status: good, Key: impeccable
◆ commit which should not be signed 1
~ (elided revisions)
│ ○ description 1
├─╯
[EOF]
");
}
#[test]
fn test_git_push_rejected_by_remote() -> TestResult {
let test_env = TestEnvironment::default();
set_up(&test_env);
let work_dir = test_env.work_dir("local");
// show repo state
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"
bookmark1: qpvuntsm 9b2e76de (empty) description 1
@origin: qpvuntsm 9b2e76de (empty) description 1
bookmark2: psuskuln 7ac95915 (empty) description 2
@origin: psuskuln 7ac95915 (empty) description 2
[EOF]
");
// create a hook on the remote that prevents pushing
let hook_path = test_env
.env_root()
.join("origin")
.join(".jj")
.join("repo")
.join("store")
.join("git")
.join("hooks")
.join("update");
std::fs::write(&hook_path, "#!/bin/sh\nexit 1")?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
std::fs::set_permissions(&hook_path, std::fs::Permissions::from_mode(0o700))?;
}
// create new commit on top of bookmark1
work_dir.run_jj(["new", "bookmark1"]).success();
work_dir.write_file("file", "file");
work_dir.run_jj(["describe", "-m=update"]).success();
// update bookmark
work_dir.run_jj(["bookmark", "move", "bookmark1"]).success();
// push bookmark
let output = work_dir.run_jj(["git", "push"]);
// The git remote sideband adds a dummy suffix of 8 spaces to attempt to clear
// any leftover data. This is done to help with cases where the line is
// rewritten.
//
// However, a common option in a lot of editors removes trailing whitespace.
// This means that anyone with that option that opens this file would make the
// following snapshot fail. Using the insta filter here normalizes the
// output.
let mut settings = insta::Settings::clone_current();
settings.add_filter(r"\s*\n", "\n");
settings.bind(|| {
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [move forward from 9b2e76de3920 to e88165908698]
remote: error: hook declined to update refs/heads/bookmark1
Warning: The remote rejected the following updates:
refs/heads/bookmark1 (reason: hook declined)
Hint: Try checking if you have permission to push to all the bookmarks.
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
");
});
Ok(())
}
#[test]
fn test_git_push_unmapped_refs() {
let test_env = TestEnvironment::default();
test_env
.run_jj_in(".", ["git", "init", "--colocate", "origin"])
.success();
test_env
.run_jj_in(".", ["git", "init", "--colocate", "local"])
.success();
let local_dir = test_env.work_dir("local");
// Set up remote with refspecs that map only specific branch
let mut data = local_dir.read_file(".git/config");
data.extend(indoc! {br#"
[remote "origin"]
url = updated-later
fetch = +refs/heads/dummy:refs/remotes/origin/dummy
"#});
local_dir.write_file(".git/config", data);
local_dir
.run_jj(["git", "remote", "set-url", "origin", "../origin"])
.success();
local_dir.run_jj(["commit", "-mcommit1"]).success();
local_dir
.run_jj(["bookmark", "set", "-r@-", "bookmark1"])
.success();
local_dir.run_jj(["commit", "-mcommit2"]).success();
local_dir
.run_jj(["bookmark", "set", "-r@-", "bookmark2"])
.success();
local_dir.run_jj(["bookmark", "track", "*"]).success();
// bookmark1 isn't mapped in Git, but can still be pushed
let output = local_dir.run_jj(["git", "push", "--bookmark=bookmark1"]);
insta::assert_snapshot!(output, @"
------- stderr -------
Changes to push to origin:
bookmark: bookmark1 [add to 5792bfac70a2]
[EOF]
");
// Remote bookmark for bookmark1 is created and exported by jj
insta::assert_snapshot!(get_bookmark_output(&local_dir), @"
bookmark1: rlvkpnrz 5792bfac (empty) commit1
@git: rlvkpnrz 5792bfac (empty) commit1
@origin: rlvkpnrz 5792bfac (empty) commit1
bookmark2: zsuskuln aa5df56a (empty) commit2
@git: zsuskuln aa5df56a (empty) commit2
@origin (not created yet)
[EOF]
");
// Make export of bookmark2 fail by creating non-empty directory
local_dir.write_file(".git/refs/remotes/origin/bookmark2/dummy.lock", "");
let output = local_dir.run_jj(["git", "push", "--bookmark=bookmark2"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Changes to push to origin:
bookmark: bookmark2 [add to aa5df56a071b]
Warning: The following bookmarks couldn't be updated locally:
bookmark2@origin: Failed to set: The change for reference "refs/remotes/origin/bookmark2" could not be committed: Directory not empty
Error: Failed to push some bookmarks
[EOF]
[exit status: 1]
"#);
// Since export failed, bookmark2@origin isn't created. If it were created,
// this command would import "deletion" of bookmark2@origin as an external
// change, and abandon commit2.
insta::assert_snapshot!(get_bookmark_output(&local_dir), @"
bookmark1: rlvkpnrz 5792bfac (empty) commit1
@git: rlvkpnrz 5792bfac (empty) commit1
@origin: rlvkpnrz 5792bfac (empty) commit1
bookmark2: zsuskuln aa5df56a (empty) commit2
@git: zsuskuln aa5df56a (empty) commit2
@origin (not created yet)
[EOF]
");
}
#[must_use]
fn get_bookmark_output(work_dir: &TestWorkDir) -> CommandOutput {
// --quiet to suppress deleted bookmarks hint
work_dir.run_jj(["bookmark", "list", "--all-remotes", "--quiet"])
}
#[must_use]
fn get_tag_output(work_dir: &TestWorkDir) -> CommandOutput {
work_dir.run_jj(["tag", "list", "--all-remotes"])
}