jj/cli/tests/test_git_fetch.rs
Scott Taylor 33ad38bfed templates: add "divergent" label to log for divergent changes
It would be good to include the word "divergent" in the log when a
change is divergent, since users are often unsure what's happening when
they see a divergent change, and giving them a term to search for would
be helpful. However, I don't think it looks good to put this label next
to the change ID itself if both are the same color, since it ends up
being hard to distinguish from the change offset at a glance. Also,
putting the label next to the change ID also messes up the alignment of
fields in the log. Therefore, I think it looks better to put the
"divergent" label at the end of the line.

Since divergence and hidden commits are similar, it makes sense for both
labels to be in the same place, so I also moved the hidden label to the
end for consistency.

One downside is that the labels are less obviously connected with the
change ID itself due to them being farther apart. I think this could be
fine, since they are still visually connected by being the same color.
2025-12-20 16:55:51 +00:00

2178 lines
72 KiB
Rust

// Copyright 2023 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 std::io::Write as _;
use indoc::indoc;
use testutils::git;
use crate::common::CommandOutput;
use crate::common::TestEnvironment;
use crate::common::TestWorkDir;
use crate::common::create_commit;
fn add_commit_to_branch(git_repo: &gix::Repository, branch: &str, message: &str) -> gix::ObjectId {
// Get current commit ID of the branch if it exists
let parents = git_repo
.find_reference(&format!("refs/heads/{branch}"))
.ok()
.and_then(|mut r| r.peel_to_commit().ok())
.map(|c| vec![c.id().detach()])
.unwrap_or_default();
git::add_commit(
git_repo,
&format!("refs/heads/{branch}"),
branch, // filename
branch.as_bytes(), // content
message,
&parents,
)
.commit_id
}
/// Creates a remote Git repo containing a bookmark with the same name
fn init_git_remote(test_env: &TestEnvironment, remote: &str) -> gix::Repository {
let git_repo_path = test_env.env_root().join(remote);
let git_repo = git::init(git_repo_path);
add_commit_to_branch(&git_repo, remote, "message");
git_repo
}
/// Add a remote containing a bookmark with the same name
fn add_git_remote(
test_env: &TestEnvironment,
work_dir: &TestWorkDir,
remote: &str,
) -> gix::Repository {
let repo = init_git_remote(test_env, remote);
work_dir
.run_jj(["git", "remote", "add", remote, &format!("../{remote}")])
.success();
repo
}
#[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_log_output(work_dir: &TestWorkDir) -> CommandOutput {
let template = indoc! {r#"
separate(" ",
commit_id.short(),
'"' ++ description.first_line() ++ '"',
bookmarks,
tags,
) ++ "\n"
"#};
work_dir.run_jj(["log", "-T", template, "-r", "all()"])
}
fn clone_git_remote_into(
test_env: &TestEnvironment,
upstream: &str,
fork: &str,
) -> gix::Repository {
let upstream_path = test_env.env_root().join(upstream);
let fork_path = test_env.env_root().join(fork);
let fork_repo = git::clone(&fork_path, upstream_path.to_str().unwrap(), Some(upstream));
// create local branch mirroring the upstream
let upstream_head = fork_repo
.find_reference(&format!("refs/remotes/{upstream}/{upstream}"))
.unwrap()
.peel_to_id()
.unwrap()
.detach();
fork_repo
.reference(
format!("refs/heads/{upstream}"),
upstream_head,
gix::refs::transaction::PreviousValue::MustNotExist,
"create tracking head",
)
.unwrap();
fork_repo
}
#[test]
fn test_git_fetch_with_default_config() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "origin");
work_dir.run_jj(["git", "fetch"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin@origin: qmyrypzk ab8b299e message
[EOF]
");
}
#[test]
fn test_git_fetch_default_remote() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "origin");
work_dir.run_jj(["git", "fetch"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin: qmyrypzk ab8b299e message
@origin: qmyrypzk ab8b299e message
[EOF]
");
}
#[test]
fn test_git_fetch_single_remote() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Hint: Fetching from the only existing remote: rem1
bookmark: rem1@rem1 [new] tracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1: ppspxspk 4acd0343 message
@rem1: ppspxspk 4acd0343 message
[EOF]
");
}
#[test]
fn test_git_fetch_single_remote_all_remotes_flag() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
work_dir.run_jj(["git", "fetch", "--all-remotes"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1: ppspxspk 4acd0343 message
@rem1: ppspxspk 4acd0343 message
[EOF]
");
}
#[test]
fn test_git_fetch_single_remote_from_arg() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
work_dir
.run_jj(["git", "fetch", "--remote", "rem1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1: ppspxspk 4acd0343 message
@rem1: ppspxspk 4acd0343 message
[EOF]
");
}
#[test]
fn test_git_fetch_single_remote_from_config() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
test_env.add_config(r#"git.fetch = "rem1""#);
work_dir.run_jj(["git", "fetch"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1: ppspxspk 4acd0343 message
@rem1: ppspxspk 4acd0343 message
[EOF]
");
}
#[test]
fn test_git_fetch_multiple_remotes() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
test_env.add_config("remotes.rem2.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
add_git_remote(&test_env, &work_dir, "rem2");
work_dir
.run_jj(["git", "fetch", "--remote", "rem1", "--remote", "rem2"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1: ppspxspk 4acd0343 message
@rem1: ppspxspk 4acd0343 message
rem2: pzqqpnpo 44c57802 message
@rem2: pzqqpnpo 44c57802 message
[EOF]
");
}
#[test]
fn test_git_fetch_with_ignored_refspecs() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let source_repo = init_git_remote(&test_env, "origin");
for branch in [
"main", "foo", "foobar", "foobaz", "bar", "sub/yes", "sub/no",
] {
add_commit_to_branch(&source_repo, branch, branch);
}
let work_dir = test_env.work_dir("repo");
std::fs::OpenOptions::new()
.append(true)
.open(work_dir.root().join(".jj/repo/store/git/config"))
.expect("failed to open config file")
.write_all(
br#"
[remote "origin"]
url = ../origin/.git
fetch = +refs/heads/main:refs/remotes/origin/main
fetch = +refs/heads/sub/*:refs/remotes/origin/sub/*
fetch = +refs/heads/foo*:refs/remotes/origin/baz*
fetch = +refs/heads/bar*:refs/tags/bar*
fetch = refs/heads/bar
fetch = ^refs/heads/sub/no
"#,
)
.expect("failed to update config file");
// Should fetch "main" and "sub/yes" by default
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Ignored refspec `refs/heads/bar` from `origin`: fetch-only refspecs are not supported
Warning: Ignored refspec `+refs/heads/bar*:refs/tags/bar*` from `origin`: only refs/remotes/ is supported for fetch destinations
Warning: Ignored refspec `+refs/heads/foo*:refs/remotes/origin/baz*` from `origin`: renaming is not supported
bookmark: main@origin [new] untracked
bookmark: sub/yes@origin [new] untracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
main@origin: wlltxvop a437242b main
sub/yes@origin: xwxtqxvy 6b64b005 sub/yes
[EOF]
");
// Can fetch ignored "sub/no" explicitly
let output = work_dir.run_jj(["git", "fetch", "--branch=sub/no"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: sub/no@origin [new] untracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
main@origin: wlltxvop a437242b main
sub/no@origin: tknwmolt f7d8b914 sub/no
sub/yes@origin: xwxtqxvy 6b64b005 sub/yes
[EOF]
");
// Forget "sub/no" without exporting the change to Git
work_dir
.run_jj(["bookmark", "forget", "--include-remotes", "sub/no"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
main@origin: wlltxvop a437242b main
sub/yes@origin: xwxtqxvy 6b64b005 sub/yes
[EOF]
");
// Should not import "sub/no" because it is ignored by default
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Ignored refspec `refs/heads/bar` from `origin`: fetch-only refspecs are not supported
Warning: Ignored refspec `+refs/heads/bar*:refs/tags/bar*` from `origin`: only refs/remotes/ is supported for fetch destinations
Warning: Ignored refspec `+refs/heads/foo*:refs/remotes/origin/baz*` from `origin`: renaming is not supported
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
main@origin: wlltxvop a437242b main
sub/yes@origin: xwxtqxvy 6b64b005 sub/yes
[EOF]
");
}
#[test]
fn test_git_fetch_with_glob() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
add_git_remote(&test_env, &work_dir, "rem2");
let output = work_dir.run_jj(["git", "fetch", "--remote", "*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: rem1@rem1 [new] untracked
bookmark: rem2@rem2 [new] untracked
[EOF]
");
}
#[test]
fn test_git_fetch_with_glob_and_exact_match() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
add_git_remote(&test_env, &work_dir, "rem2");
add_git_remote(&test_env, &work_dir, "upstream1");
add_git_remote(&test_env, &work_dir, "upstream2");
add_git_remote(&test_env, &work_dir, "origin");
let output = work_dir.run_jj(["git", "fetch", "--remote=rem*", "--remote=origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: origin@origin [new] untracked
bookmark: rem1@rem1 [new] untracked
bookmark: rem2@rem2 [new] untracked
[EOF]
");
}
#[test]
fn test_git_fetch_with_glob_from_config() {
let test_env = TestEnvironment::default();
test_env.add_config(r#"git.fetch = "rem*""#);
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
add_git_remote(&test_env, &work_dir, "rem2");
add_git_remote(&test_env, &work_dir, "upstream");
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: rem1@rem1 [new] untracked
bookmark: rem2@rem2 [new] untracked
[EOF]
");
}
#[test]
fn test_git_fetch_with_glob_with_no_matching_remotes() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "upstream");
let output = work_dir.run_jj(["git", "fetch", "--remote=rem*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: No git remotes to fetch from
[EOF]
[exit status: 1]
");
// No remote should have been fetched as part of the failing transaction
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
}
#[test]
fn test_git_fetch_all_remotes() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
test_env.add_config("remotes.rem2.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
add_git_remote(&test_env, &work_dir, "rem2");
// add empty [remote "rem3"] section to .git/config, which should be ignored
work_dir
.run_jj(["git", "remote", "add", "rem3", "../unknown"])
.success();
work_dir
.run_jj(["git", "remote", "remove", "rem3"])
.success();
work_dir.run_jj(["git", "fetch", "--all-remotes"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1: ppspxspk 4acd0343 message
@rem1: ppspxspk 4acd0343 message
rem2: pzqqpnpo 44c57802 message
@rem2: pzqqpnpo 44c57802 message
[EOF]
");
}
#[test]
fn test_git_fetch_multiple_remotes_from_config() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
test_env.add_config("remotes.rem2.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
add_git_remote(&test_env, &work_dir, "rem2");
test_env.add_config(r#"git.fetch = ["rem1", "rem2"]"#);
work_dir.run_jj(["git", "fetch"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1: ppspxspk 4acd0343 message
@rem1: ppspxspk 4acd0343 message
rem2: pzqqpnpo 44c57802 message
@rem2: pzqqpnpo 44c57802 message
[EOF]
");
}
#[test]
fn test_git_fetch_no_matching_remote() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["git", "fetch", "--remote", "rem1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching remotes for names: rem1
Error: No git remotes to fetch from
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["git", "fetch", "--remote=rem1", "--remote=rem2"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching remotes for names: rem1, rem2
Error: No git remotes to fetch from
[EOF]
[exit status: 1]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
}
#[test]
fn test_git_fetch_nonexistent_remote() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
let output = work_dir.run_jj(["git", "fetch", "--remote", "rem1", "--remote", "rem2"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching remotes for names: rem2
bookmark: rem1@rem1 [new] untracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1@rem1: ppspxspk 4acd0343 message
[EOF]
");
}
#[test]
fn test_git_fetch_nonexistent_remote_from_config() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
test_env.add_config(r#"git.fetch = ["rem1", "rem2"]"#);
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching remotes for names: rem2
bookmark: rem1@rem1 [new] untracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1@rem1: ppspxspk 4acd0343 message
[EOF]
");
}
#[test]
fn test_git_fetch_from_remote_named_git() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.bar.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("repo");
init_git_remote(&test_env, "git");
git::init(work_dir.root());
git::add_remote(work_dir.root(), "git", "../git");
// Existing remote named 'git' shouldn't block the repo initialization.
work_dir.run_jj(["git", "init", "--git-repo=."]).success();
// Try fetching from the remote named 'git'.
let output = work_dir.run_jj(["git", "fetch", "--remote=git"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
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]
");
// Fetch remote refs by using the git CLI.
git::fetch(work_dir.root(), "git");
// Implicit import shouldn't fail because of the remote ref.
let output = work_dir.run_jj(["bookmark", "list", "--all-remotes"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Failed to import some Git refs:
refs/remotes/git/git
Hint: Git remote named 'git' is reserved for local Git repository.
Use `jj git remote rename` to give a different name.
[EOF]
");
// Explicit import also works. Warnings are printed twice because this is a
// colocated workspace. That should be fine since "jj git import" wouldn't
// be used in colocated environment.
insta::assert_snapshot!(work_dir.run_jj(["git", "import"]), @r"
------- stderr -------
Warning: Failed to import some Git refs:
refs/remotes/git/git
Hint: Git remote named 'git' is reserved for local Git repository.
Use `jj git remote rename` to give a different name.
Warning: Failed to import some Git refs:
refs/remotes/git/git
Hint: Git remote named 'git' is reserved for local Git repository.
Use `jj git remote rename` to give a different name.
Nothing changed.
[EOF]
");
// The remote can be renamed, and the ref can be imported.
work_dir
.run_jj(["git", "remote", "rename", "git", "bar"])
.success();
let output = work_dir.run_jj(["bookmark", "list", "--all-remotes"]);
insta::assert_snapshot!(output, @r"
git: vkponlun 400c483d message
@bar: vkponlun 400c483d message
@git: vkponlun 400c483d message
[EOF]
------- stderr -------
Done importing changes from the underlying Git repo.
[EOF]
");
}
#[test]
fn test_git_fetch_from_remote_with_slashes() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("repo");
init_git_remote(&test_env, "source");
git::init(work_dir.root());
git::add_remote(work_dir.root(), "slash/origin", "../source");
// Existing remote with slash shouldn't block the repo initialization.
work_dir.run_jj(["git", "init", "--git-repo=."]).success();
// Try fetching from the remote named 'git'.
let output = work_dir.run_jj(["git", "fetch", "--remote=slash/origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
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_fetch_prune_before_updating_tips() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let git_repo = add_git_remote(&test_env, &work_dir, "origin");
work_dir.run_jj(["git", "fetch"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin: qmyrypzk ab8b299e message
@origin: qmyrypzk ab8b299e message
[EOF]
");
// Remove origin bookmark in git repo and create origin/subname
let mut origin_reference = git_repo.find_reference("refs/heads/origin").unwrap();
let commit_id = origin_reference.peel_to_commit().unwrap().id().detach();
origin_reference.delete().unwrap();
git_repo
.reference(
"refs/heads/origin/subname",
commit_id,
gix::refs::transaction::PreviousValue::MustNotExist,
"create new reference",
)
.unwrap();
work_dir.run_jj(["git", "fetch"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin/subname: qmyrypzk ab8b299e message
@origin: qmyrypzk ab8b299e message
[EOF]
");
}
#[test]
fn test_git_fetch_conflicting_bookmarks() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "rem1");
// Create a rem1 bookmark locally
work_dir.run_jj(["new", "root()"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "rem1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1: kkmpptxz 2b17ac71 (empty) (no description set)
@rem1 (not created yet)
[EOF]
");
work_dir
.run_jj(["git", "fetch", "--remote", "rem1", "--branch", "*"])
.success();
// This should result in a CONFLICTED bookmark
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1 (conflicted):
+ kkmpptxz 2b17ac71 (empty) (no description set)
+ ppspxspk 4acd0343 message
@rem1 (behind by 1 commits): ppspxspk 4acd0343 message
[EOF]
");
}
#[test]
fn test_git_fetch_conflicting_bookmarks_colocated() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
let work_dir = test_env.work_dir("repo");
git::init(work_dir.root());
// create_colocated_repo_and_bookmarks_from_trunk1(&test_env, &repo_path);
work_dir
.run_jj(["git", "init", "--git-repo", "."])
.success();
add_git_remote(&test_env, &work_dir, "rem1");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
// Create a rem1 bookmark locally
work_dir.run_jj(["new", "root()"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "rem1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1: zsuskuln c2934cfb (empty) (no description set)
@git: zsuskuln c2934cfb (empty) (no description set)
@rem1 (not created yet)
[EOF]
");
work_dir
.run_jj(["git", "fetch", "--remote", "rem1", "--branch", "rem1"])
.success();
// This should result in a CONFLICTED bookmark
// See https://github.com/jj-vcs/jj/pull/1146#discussion_r1112372340 for the bug this tests for.
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
rem1 (conflicted):
+ zsuskuln c2934cfb (empty) (no description set)
+ ppspxspk 4acd0343 message
@git (behind by 1 commits): zsuskuln c2934cfb (empty) (no description set)
@rem1 (behind by 1 commits): ppspxspk 4acd0343 message
[EOF]
");
}
// Helper functions to test obtaining multiple bookmarks at once and changed
// bookmarks
fn create_colocated_repo_and_bookmarks_from_trunk1(work_dir: &TestWorkDir) -> String {
// Create a colocated workspace in `source` to populate it more easily
work_dir
.run_jj(["git", "init", "--git-repo", "."])
.success();
create_commit(work_dir, "trunk1", &[]);
create_commit(work_dir, "a1", &["trunk1"]);
create_commit(work_dir, "a2", &["trunk1"]);
create_commit(work_dir, "b", &["trunk1"]);
format!(
" ===== Source git repo contents =====\n{}",
get_log_output(work_dir)
)
}
fn create_trunk2_and_rebase_bookmarks(work_dir: &TestWorkDir) -> String {
create_commit(work_dir, "trunk2", &["trunk1"]);
for br in ["a1", "a2", "b"] {
work_dir
.run_jj(["rebase", "-b", br, "-o", "trunk2"])
.success();
}
format!(
" ===== Source git repo contents =====\n{}",
get_log_output(work_dir)
)
}
#[test]
fn test_git_fetch_all() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
let source_dir = test_env.work_dir("source");
git::init(source_dir.root());
// Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Fetching into new repo in "$TEST_ENV/target"
Nothing changed.
[EOF]
"#);
let target_dir = test_env.work_dir("target");
let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir);
insta::assert_snapshot!(source_log, @r#"
===== Source git repo contents =====
@ bc83465a3090 "b" b
│ ○ d4d535f1d579 "a2" a2
├─╯
│ ○ c8303692b8e2 "a1" a1
├─╯
○ 382881770501 "trunk1" trunk1
◆ 000000000000 ""
[EOF]
"#);
// Nothing in our repo before the fetch
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
◆ 000000000000 ""
[EOF]
"#);
insta::assert_snapshot!(get_bookmark_output(&target_dir), @"");
let output = target_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a1@origin [new] tracked
bookmark: a2@origin [new] tracked
bookmark: b@origin [new] tracked
bookmark: trunk1@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&target_dir), @r"
a1: mzvwutvl c8303692 a1
@origin: mzvwutvl c8303692 a1
a2: yqosqzyt d4d535f1 a2
@origin: yqosqzyt d4d535f1 a2
b: yostqsxw bc83465a b
@origin: yostqsxw bc83465a b
trunk1: kkmpptxz 38288177 trunk1
@origin: kkmpptxz 38288177 trunk1
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ │ ○ d4d535f1d579 "a2" a2
│ ├─╯
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ ○ 382881770501 "trunk1" trunk1
├─╯
◆ 000000000000 ""
[EOF]
"#);
// ==== Change both repos ====
// First, change the target repo:
let source_log = create_trunk2_and_rebase_bookmarks(&source_dir);
insta::assert_snapshot!(source_log, @r#"
===== Source git repo contents =====
○ 6fc6fe17dbee "b" b
│ ○ baad96fead6c "a2" a2
├─╯
│ ○ 798c5e2435e1 "a1" a1
├─╯
@ e80d998ab04b "trunk2" trunk2
○ 382881770501 "trunk1" trunk1
◆ 000000000000 ""
[EOF]
"#);
// Change a bookmark in the source repo as well, so that it becomes conflicted.
target_dir
.run_jj(["describe", "b", "-m=new_descr_for_b_to_create_conflict"])
.success();
// Our repo before and after fetch
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ 0fbbc495357c "new_descr_for_b_to_create_conflict" b*
│ │ ○ d4d535f1d579 "a2" a2
│ ├─╯
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ ○ 382881770501 "trunk1" trunk1
├─╯
◆ 000000000000 ""
[EOF]
"#);
insta::assert_snapshot!(get_bookmark_output(&target_dir), @r"
a1: mzvwutvl c8303692 a1
@origin: mzvwutvl c8303692 a1
a2: yqosqzyt d4d535f1 a2
@origin: yqosqzyt d4d535f1 a2
b: yostqsxw 0fbbc495 new_descr_for_b_to_create_conflict
@origin (ahead by 1 commits, behind by 1 commits): yostqsxw/1 bc83465a (hidden) b
trunk1: kkmpptxz 38288177 trunk1
@origin: kkmpptxz 38288177 trunk1
[EOF]
");
let output = target_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a1@origin [updated] tracked
bookmark: a2@origin [updated] tracked
bookmark: b@origin [updated] tracked
bookmark: trunk2@origin [new] tracked
Abandoned 2 commits that are no longer reachable.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&target_dir), @r"
a1: mzvwutvl 798c5e24 a1
@origin: mzvwutvl 798c5e24 a1
a2: yqosqzyt baad96fe a2
@origin: yqosqzyt baad96fe a2
b (conflicted):
- yostqsxw/2 bc83465a (hidden) b
+ yostqsxw/1 0fbbc495 (divergent) new_descr_for_b_to_create_conflict
+ yostqsxw/0 6fc6fe17 (divergent) b
@origin (behind by 1 commits): yostqsxw/0 6fc6fe17 (divergent) b
trunk1: kkmpptxz 38288177 trunk1
@origin: kkmpptxz 38288177 trunk1
trunk2: uyznsvlq e80d998a trunk2
@origin: uyznsvlq e80d998a trunk2
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ 6fc6fe17dbee "b" b?? b@origin
│ │ ○ baad96fead6c "a2" a2
│ ├─╯
│ │ ○ 798c5e2435e1 "a1" a1
│ ├─╯
│ ○ e80d998ab04b "trunk2" trunk2
│ │ ○ 0fbbc495357c "new_descr_for_b_to_create_conflict" b??
│ ├─╯
│ ○ 382881770501 "trunk1" trunk1
├─╯
◆ 000000000000 ""
[EOF]
"#);
}
#[test]
fn test_git_fetch_some_of_many_bookmarks() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
let source_dir = test_env.work_dir("source");
git::init(source_dir.root());
// Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Fetching into new repo in "$TEST_ENV/target"
Nothing changed.
[EOF]
"#);
let target_dir = test_env.work_dir("target");
let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir);
insta::assert_snapshot!(source_log, @r#"
===== Source git repo contents =====
@ bc83465a3090 "b" b
│ ○ d4d535f1d579 "a2" a2
├─╯
│ ○ c8303692b8e2 "a1" a1
├─╯
○ 382881770501 "trunk1" trunk1
◆ 000000000000 ""
[EOF]
"#);
// Test an error message
let output = target_dir.run_jj(["git", "fetch", "--branch", "'^:a*'"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Invalid branch pattern provided. When fetching, branch names and globs may not contain the characters `:`, `^`, `?`, `[`, `]`
[EOF]
[exit status: 1]
");
let output = target_dir.run_jj(["git", "fetch", "--branch", "exact:a*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Invalid branch pattern provided. When fetching, branch names and globs may not contain the characters `:`, `^`, `?`, `[`, `]`
[EOF]
[exit status: 1]
");
// Nothing in our repo before the fetch
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
◆ 000000000000 ""
[EOF]
"#);
// Fetch one bookmark...
let output = target_dir.run_jj(["git", "fetch", "--branch", "b"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: b@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ ○ 382881770501 "trunk1"
├─╯
◆ 000000000000 ""
[EOF]
"#);
// ...check what the intermediate state looks like...
insta::assert_snapshot!(get_bookmark_output(&target_dir), @r"
b: yostqsxw bc83465a b
@origin: yostqsxw bc83465a b
[EOF]
");
// ...then fetch two others with a glob.
let output = target_dir.run_jj(["git", "fetch", "--branch", "a*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a1@origin [new] tracked
bookmark: a2@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ d4d535f1d579 "a2" a2
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ │ ○ bc83465a3090 "b" b
│ ├─╯
│ ○ 382881770501 "trunk1"
├─╯
◆ 000000000000 ""
[EOF]
"#);
// Fetching the same bookmark again
let output = target_dir.run_jj(["git", "fetch", "--branch", "a1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ d4d535f1d579 "a2" a2
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ │ ○ bc83465a3090 "b" b
│ ├─╯
│ ○ 382881770501 "trunk1"
├─╯
◆ 000000000000 ""
[EOF]
"#);
// ==== Change both repos ====
// First, change the target repo:
let source_log = create_trunk2_and_rebase_bookmarks(&source_dir);
insta::assert_snapshot!(source_log, @r#"
===== Source git repo contents =====
○ 2b30dbc93959 "b" b
│ ○ 841140b152fc "a2" a2
├─╯
│ ○ bc7e74c21d43 "a1" a1
├─╯
@ 756be1d31c41 "trunk2" trunk2
○ 382881770501 "trunk1" trunk1
◆ 000000000000 ""
[EOF]
"#);
// Change a bookmark in the source repo as well, so that it becomes conflicted.
target_dir
.run_jj(["describe", "b", "-m=new_descr_for_b_to_create_conflict"])
.success();
// Our repo before and after fetch of two bookmarks
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ c62db3119722 "new_descr_for_b_to_create_conflict" b*
│ │ ○ d4d535f1d579 "a2" a2
│ ├─╯
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ ○ 382881770501 "trunk1"
├─╯
◆ 000000000000 ""
[EOF]
"#);
let output = target_dir.run_jj(["git", "fetch", "--branch=~(a2 | trunk*)"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a1@origin [updated] tracked
bookmark: b@origin [updated] tracked
Abandoned 1 commits that are no longer reachable.
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ 2b30dbc93959 "b" b?? b@origin
│ │ ○ bc7e74c21d43 "a1" a1
│ ├─╯
│ ○ 756be1d31c41 "trunk2"
│ │ ○ c62db3119722 "new_descr_for_b_to_create_conflict" b??
│ ├─╯
│ │ ○ d4d535f1d579 "a2" a2
│ ├─╯
│ ○ 382881770501 "trunk1"
├─╯
◆ 000000000000 ""
[EOF]
"#);
// We left a2 where it was before, let's see how `jj bookmark list` sees this.
insta::assert_snapshot!(get_bookmark_output(&target_dir), @r"
a1: mzvwutvl bc7e74c2 a1
@origin: mzvwutvl bc7e74c2 a1
a2: yqosqzyt d4d535f1 a2
@origin: yqosqzyt d4d535f1 a2
b (conflicted):
- yostqsxw/2 bc83465a (hidden) b
+ yostqsxw/1 c62db311 (divergent) new_descr_for_b_to_create_conflict
+ yostqsxw/0 2b30dbc9 (divergent) b
@origin (behind by 1 commits): yostqsxw/0 2b30dbc9 (divergent) b
[EOF]
");
// Now, let's fetch a2 and double-check that fetching a1 and b again doesn't do
// anything.
let output = target_dir.run_jj(["git", "fetch", "--branch", "b", "--branch", "a*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a2@origin [updated] tracked
Abandoned 1 commits that are no longer reachable.
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ 841140b152fc "a2" a2
│ │ ○ 2b30dbc93959 "b" b?? b@origin
│ ├─╯
│ │ ○ bc7e74c21d43 "a1" a1
│ ├─╯
│ ○ 756be1d31c41 "trunk2"
│ │ ○ c62db3119722 "new_descr_for_b_to_create_conflict" b??
│ ├─╯
│ ○ 382881770501 "trunk1"
├─╯
◆ 000000000000 ""
[EOF]
"#);
insta::assert_snapshot!(get_bookmark_output(&target_dir), @r"
a1: mzvwutvl bc7e74c2 a1
@origin: mzvwutvl bc7e74c2 a1
a2: yqosqzyt 841140b1 a2
@origin: yqosqzyt 841140b1 a2
b (conflicted):
- yostqsxw/2 bc83465a (hidden) b
+ yostqsxw/1 c62db311 (divergent) new_descr_for_b_to_create_conflict
+ yostqsxw/0 2b30dbc9 (divergent) b
@origin (behind by 1 commits): yostqsxw/0 2b30dbc9 (divergent) b
[EOF]
");
}
#[test]
fn test_git_fetch_bookmarks_some_missing() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.add_config("remotes.rem1.auto-track-bookmarks = '*'");
test_env.add_config("remotes.rem2.auto-track-bookmarks = '*'");
test_env.add_config("remotes.rem3.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "origin");
add_git_remote(&test_env, &work_dir, "rem1");
add_git_remote(&test_env, &work_dir, "rem2");
add_git_remote(&test_env, &work_dir, "rem3");
// single missing bookmark, implicit remotes (@origin)
let output = work_dir.run_jj(["git", "fetch", "--branch", "noexist"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching branches found on any specified/configured remote: noexist
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
// multiple missing bookmarks, implicit remotes (@origin)
let output = work_dir.run_jj([
"git", "fetch", "--branch", "noexist1", "--branch", "noexist2",
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching branches found on any specified/configured remote: noexist1, noexist2
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
// single existing bookmark, implicit remotes (@origin)
let output = work_dir.run_jj(["git", "fetch", "--branch", "origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: origin@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin: qmyrypzk ab8b299e message
@origin: qmyrypzk ab8b299e message
[EOF]
");
// multiple existing bookmark, explicit remotes, each bookmark is only in one
// remote.
let output = work_dir.run_jj([
"git", "fetch", "--branch", "rem1", "--branch", "rem2", "--branch", "rem3", "--remote",
"rem1", "--remote", "rem2", "--remote", "rem3",
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: rem1@rem1 [new] tracked
bookmark: rem2@rem2 [new] tracked
bookmark: rem3@rem3 [new] tracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin: qmyrypzk ab8b299e message
@origin: qmyrypzk ab8b299e message
rem1: ppspxspk 4acd0343 message
@rem1: ppspxspk 4acd0343 message
rem2: pzqqpnpo 44c57802 message
@rem2: pzqqpnpo 44c57802 message
rem3: wrzwlmys 45a3faef message
@rem3: wrzwlmys 45a3faef message
[EOF]
");
// multiple bookmarks, one exists, one doesn't
let output = work_dir.run_jj([
"git", "fetch", "--branch", "rem1", "--branch", "notexist", "--remote", "rem1",
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching branches found on any specified/configured remote: notexist
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin: qmyrypzk ab8b299e message
@origin: qmyrypzk ab8b299e message
rem1: ppspxspk 4acd0343 message
@rem1: ppspxspk 4acd0343 message
rem2: pzqqpnpo 44c57802 message
@rem2: pzqqpnpo 44c57802 message
rem3: wrzwlmys 45a3faef message
@rem3: wrzwlmys 45a3faef message
[EOF]
");
}
#[test]
fn test_git_fetch_bookmarks_missing_with_subprocess_localized_message() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "origin");
// "fatal: couldn't find remote ref %s" shouldn't be localized.
let output = work_dir.run_jj_with(|cmd| {
cmd.args(["git", "fetch", "--branch=unknown"])
// Initialize locale as "en_US" which is the most common.
.env("LC_ALL", "en_US.UTF-8")
// Set some other locale variables for testing.
.env("LC_MESSAGES", "en_US.UTF-8")
.env("LANG", "en_US.UTF-8")
// GNU gettext prioritizes LANGUAGE if translation is enabled. It works
// no matter if system locale exists or not.
.env("LANGUAGE", "zh_TW")
});
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching branches found on any specified/configured remote: unknown
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_fetch_unsupported_branch_patterns() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "origin");
let output = work_dir.run_jj(["git", "fetch", "--branch=x&y|z"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Cannot use `&` in sub expression
Hint: Specify patterns in `(positive | ...) & ~(negative | ...)` form.
[EOF]
[exit status: 1]
");
// Unsupported glob pattern in negative refspecs
let output = work_dir.run_jj(["git", "fetch", "--branch=~'[xy]'"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Invalid branch pattern provided. When fetching, branch names and globs may not contain the characters `:`, `^`, `?`, `[`, `]`
[EOF]
[exit status: 1]
");
}
// See `test_undo_restore_commands.rs` for fetch-undo-push and fetch-undo-fetch
// of the same bookmarks for various kinds of undo.
#[test]
fn test_git_fetch_undo() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let source_dir = test_env.work_dir("source");
git::init(source_dir.root());
// Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Fetching into new repo in "$TEST_ENV/target"
Nothing changed.
[EOF]
"#);
let target_dir = test_env.work_dir("target");
create_colocated_repo_and_bookmarks_from_trunk1(&source_dir);
source_dir
.run_jj(["tag", "set", "-rtrunk1", "tag1"])
.success();
insta::assert_snapshot!(get_log_output(&source_dir), @r#"
@ bc83465a3090 "b" b
│ ○ d4d535f1d579 "a2" a2
├─╯
│ ○ c8303692b8e2 "a1" a1
├─╯
◆ 382881770501 "trunk1" trunk1 tag1
◆ 000000000000 ""
[EOF]
"#);
// Fetch 2 bookmarks and tags
let output = target_dir.run_jj(["git", "fetch", "--branch", "b", "--branch", "a1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a1@origin [new] tracked
bookmark: b@origin [new] tracked
tag: tag1@git [new]
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ ◆ 382881770501 "trunk1" tag1
├─╯
◆ 000000000000 ""
[EOF]
"#);
let output = target_dir.run_jj(["undo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Restored to operation: 8aeac520a856 (2001-02-03 08:05:07) add git remote origin
[EOF]
");
// The undo works as expected
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
◆ 000000000000 ""
[EOF]
"#);
// Now try to fetch just one bookmark and tags
let output = target_dir.run_jj(["git", "fetch", "--branch", "b"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: b@origin [new] tracked
tag: tag1@git [new]
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ ◆ 382881770501 "trunk1" tag1
├─╯
◆ 000000000000 ""
[EOF]
"#);
}
// Compare to `test_git_import_undo` in test_git_import_export
// TODO: Explain why these behaviors are useful
#[test]
fn test_fetch_undo_what() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let source_dir = test_env.work_dir("source");
git::init(source_dir.root());
// Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Fetching into new repo in "$TEST_ENV/target"
Nothing changed.
[EOF]
"#);
let work_dir = test_env.work_dir("target");
let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir);
insta::assert_snapshot!(source_log, @r#"
===== Source git repo contents =====
@ bc83465a3090 "b" b
│ ○ d4d535f1d579 "a2" a2
├─╯
│ ○ c8303692b8e2 "a1" a1
├─╯
○ 382881770501 "trunk1" trunk1
◆ 000000000000 ""
[EOF]
"#);
// Initial state we will try to return to after `op restore`. There are no
// bookmarks.
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
let base_operation_id = work_dir.current_operation_id();
// Fetch a bookmark
let output = work_dir.run_jj(["git", "fetch", "--branch", "b"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: b@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ ○ 382881770501 "trunk1"
├─╯
◆ 000000000000 ""
[EOF]
"#);
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
b: yostqsxw bc83465a b
@origin: yostqsxw bc83465a b
[EOF]
");
// We can undo the change in the repo without moving the remote-tracking
// bookmark
let output = work_dir.run_jj(["op", "restore", "--what", "repo", &base_operation_id]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Restored to operation: 8aeac520a856 (2001-02-03 08:05:07) add git remote origin
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
b (deleted)
@origin: yostqsxw/0 bc83465a (hidden) b
[EOF]
");
// Now, let's demo restoring just the remote-tracking bookmark. First, let's
// change our local repo state...
work_dir
.run_jj(["bookmark", "c", "-r@", "newbookmark"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
b (deleted)
@origin: yostqsxw/0 bc83465a (hidden) b
newbookmark: qpvuntsm e8849ae1 (empty) (no description set)
@origin (not created yet)
[EOF]
");
// Restoring just the remote-tracking state will not affect `newbookmark`, but
// will eliminate `b@origin`.
let output = work_dir.run_jj([
"op",
"restore",
"--what",
"remote-tracking",
&base_operation_id,
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Restored to operation: 8aeac520a856 (2001-02-03 08:05:07) add git remote origin
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
newbookmark: qpvuntsm e8849ae1 (empty) (no description set)
[EOF]
");
}
#[test]
fn test_git_fetch_remove_fetch() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "origin");
work_dir
.run_jj(["bookmark", "create", "-r@", "origin"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin: qpvuntsm e8849ae1 (empty) (no description set)
@origin (not created yet)
[EOF]
");
work_dir.run_jj(["git", "fetch"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin (conflicted):
+ qpvuntsm e8849ae1 (empty) (no description set)
+ qmyrypzk ab8b299e message
@origin (behind by 1 commits): qmyrypzk ab8b299e message
[EOF]
");
work_dir
.run_jj(["git", "remote", "remove", "origin"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin (conflicted):
+ qpvuntsm e8849ae1 (empty) (no description set)
+ qmyrypzk ab8b299e message
[EOF]
");
work_dir
.run_jj(["git", "remote", "add", "origin", "../origin"])
.success();
// Check that origin@origin is properly recreated
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: origin@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin (conflicted):
+ qpvuntsm e8849ae1 (empty) (no description set)
+ qmyrypzk ab8b299e message
@origin (behind by 1 commits): qmyrypzk ab8b299e message
[EOF]
");
}
#[test]
fn test_git_fetch_rename_fetch() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
add_git_remote(&test_env, &work_dir, "origin");
work_dir
.run_jj(["bookmark", "create", "-r@", "origin"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin: qpvuntsm e8849ae1 (empty) (no description set)
@origin (not created yet)
[EOF]
");
work_dir.run_jj(["git", "fetch"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin (conflicted):
+ qpvuntsm e8849ae1 (empty) (no description set)
+ qmyrypzk ab8b299e message
@origin (behind by 1 commits): qmyrypzk ab8b299e message
[EOF]
");
work_dir
.run_jj(["git", "remote", "rename", "origin", "upstream"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
origin (conflicted):
+ qpvuntsm e8849ae1 (empty) (no description set)
+ qmyrypzk ab8b299e message
@upstream (behind by 1 commits): qmyrypzk ab8b299e message
[EOF]
");
// Check that jj indicates that nothing has changed
let output = work_dir.run_jj(["git", "fetch", "--remote", "upstream"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_fetch_removed_bookmark() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let source_dir = test_env.work_dir("source");
git::init(source_dir.root());
// Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Fetching into new repo in "$TEST_ENV/target"
Nothing changed.
[EOF]
"#);
let target_dir = test_env.work_dir("target");
let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir);
insta::assert_snapshot!(source_log, @r#"
===== Source git repo contents =====
@ bc83465a3090 "b" b
│ ○ d4d535f1d579 "a2" a2
├─╯
│ ○ c8303692b8e2 "a1" a1
├─╯
○ 382881770501 "trunk1" trunk1
◆ 000000000000 ""
[EOF]
"#);
// Fetch all bookmarks
let output = target_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a1@origin [new] tracked
bookmark: a2@origin [new] tracked
bookmark: b@origin [new] tracked
bookmark: trunk1@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ │ ○ d4d535f1d579 "a2" a2
│ ├─╯
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ ○ 382881770501 "trunk1" trunk1
├─╯
◆ 000000000000 ""
[EOF]
"#);
// Remove a2 bookmark in origin
source_dir
.run_jj(["bookmark", "forget", "--include-remotes", "a2"])
.success();
// Fetch bookmark a1 from origin and check that a2 is still there
let output = target_dir.run_jj(["git", "fetch", "--branch", "a1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ │ ○ d4d535f1d579 "a2" a2
│ ├─╯
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ ○ 382881770501 "trunk1" trunk1
├─╯
◆ 000000000000 ""
[EOF]
"#);
// Fetch bookmarks a2 from origin, and check that it has been removed locally
let output = target_dir.run_jj(["git", "fetch", "--branch", "a2"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a2@origin [deleted] untracked
Abandoned 1 commits that are no longer reachable.
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ ○ 382881770501 "trunk1" trunk1
├─╯
◆ 000000000000 ""
[EOF]
"#);
}
#[test]
fn test_git_fetch_removed_parent_bookmark() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
let source_dir = test_env.work_dir("source");
git::init(source_dir.root());
// Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Fetching into new repo in "$TEST_ENV/target"
Nothing changed.
[EOF]
"#);
let target_dir = test_env.work_dir("target");
let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir);
insta::assert_snapshot!(source_log, @r#"
===== Source git repo contents =====
@ bc83465a3090 "b" b
│ ○ d4d535f1d579 "a2" a2
├─╯
│ ○ c8303692b8e2 "a1" a1
├─╯
○ 382881770501 "trunk1" trunk1
◆ 000000000000 ""
[EOF]
"#);
// Fetch all bookmarks
let output = target_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a1@origin [new] tracked
bookmark: a2@origin [new] tracked
bookmark: b@origin [new] tracked
bookmark: trunk1@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ │ ○ d4d535f1d579 "a2" a2
│ ├─╯
│ │ ○ c8303692b8e2 "a1" a1
│ ├─╯
│ ○ 382881770501 "trunk1" trunk1
├─╯
◆ 000000000000 ""
[EOF]
"#);
// Remove all bookmarks in origin.
source_dir
.run_jj(["bookmark", "forget", "--include-remotes", "*"])
.success();
// Fetch bookmarks master, trunk1 and a1 from origin and check that only those
// bookmarks have been removed and that others were not rebased because of
// abandoned commits.
let output = target_dir.run_jj([
"git", "fetch", "--branch", "master", "--branch", "trunk1", "--branch", "a1",
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: a1@origin [deleted] untracked
bookmark: trunk1@origin [deleted] untracked
Abandoned 1 commits that are no longer reachable.
Warning: No matching branches found on any specified/configured remote: master
[EOF]
");
insta::assert_snapshot!(get_log_output(&target_dir), @r#"
@ e8849ae12c70 ""
│ ○ bc83465a3090 "b" b
│ │ ○ d4d535f1d579 "a2" a2
│ ├─╯
│ ○ 382881770501 "trunk1"
├─╯
◆ 000000000000 ""
[EOF]
"#);
}
#[test]
fn test_git_fetch_remote_only_bookmark() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Create non-empty git repo to add as a remote
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git::init(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
// Create a commit and a bookmark in the git repo
let commit_result = git::add_commit(
&git_repo,
"refs/heads/feature1",
"file",
b"content",
"message",
&[],
);
// Fetch using remotes.origin.auto-track-bookmarks = '*'
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
work_dir
.run_jj(["git", "fetch", "--remote=origin"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qomsplrm ebeb70d8 message
@origin: qomsplrm ebeb70d8 message
[EOF]
");
git::write_commit(
&git_repo,
"refs/heads/feature2",
commit_result.tree_id,
"message",
&[],
);
// Fetch using remotes.origin.auto-track-bookmarks = '~*'
test_env.add_config("remotes.origin.auto-track-bookmarks = '~*'");
work_dir
.run_jj(["git", "fetch", "--remote=origin"])
.success();
insta::assert_snapshot!(get_log_output(&work_dir), @r#"
@ e8849ae12c70 ""
│ ◆ ebeb70d8c5f9 "message" feature1 feature2@origin
├─╯
◆ 000000000000 ""
[EOF]
"#);
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qomsplrm ebeb70d8 message
@origin: qomsplrm ebeb70d8 message
feature2@origin: qomsplrm ebeb70d8 message
[EOF]
");
}
#[test]
fn test_git_fetch_preserve_commits_across_repos() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.upstream.auto-track-bookmarks = '*'");
test_env.add_config("remotes.fork.auto-track-bookmarks = '*'");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let upstream_repo = add_git_remote(&test_env, &work_dir, "upstream");
let fork_path = test_env.env_root().join("fork");
let fork_repo = clone_git_remote_into(&test_env, "upstream", "fork");
work_dir
.run_jj(["git", "remote", "add", "fork", "../fork"])
.success();
// add commit to fork remote in another branch
add_commit_to_branch(&fork_repo, "feature", "message");
// fetch remote bookmarks
work_dir
.run_jj(["git", "fetch", "--remote=fork", "--remote=upstream"])
.success();
insta::assert_snapshot!(get_log_output(&work_dir), @r#"
@ e8849ae12c70 ""
│ ○ bcd7cd779791 "message" upstream
├─╯
│ ○ 16ec9ef2877a "message" feature
├─╯
◆ 000000000000 ""
[EOF]
"#);
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature: srwrtuky 16ec9ef2 message
@fork: srwrtuky 16ec9ef2 message
upstream: zkvzklqn bcd7cd77 message
@fork: zkvzklqn bcd7cd77 message
@upstream: zkvzklqn bcd7cd77 message
[EOF]
");
// merge fork/feature into the upstream/upstream
git::add_remote(upstream_repo.git_dir(), "fork", fork_path.to_str().unwrap());
git::fetch(upstream_repo.git_dir(), "fork");
let base_id = upstream_repo
.find_reference("refs/heads/upstream")
.unwrap()
.peel_to_commit()
.unwrap()
.id()
.detach();
let fork_id = upstream_repo
.find_reference("refs/remotes/fork/feature")
.unwrap()
.peel_to_commit()
.unwrap()
.id()
.detach();
git::write_commit(
&upstream_repo,
"refs/heads/upstream",
upstream_repo.empty_tree().id().detach(),
"merge",
&[base_id, fork_id],
);
// remove branch on the fork
fork_repo
.find_reference("refs/heads/feature")
.unwrap()
.delete()
.unwrap();
// fetch again on the jj repo, first looking at fork and then at upstream
work_dir
.run_jj(["git", "fetch", "--remote=fork", "--remote=upstream"])
.success();
insta::assert_snapshot!(get_log_output(&work_dir), @r#"
@ e8849ae12c70 ""
│ ○ f3e9250bd003 "merge" upstream*
│ ├─╮
│ │ ○ 16ec9ef2877a "message"
├───╯
│ ○ bcd7cd779791 "message" upstream@fork
├─╯
◆ 000000000000 ""
[EOF]
"#);
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
upstream: trrkvuqr f3e9250b merge
@fork (behind by 2 commits): zkvzklqn bcd7cd77 message
@upstream: trrkvuqr f3e9250b merge
[EOF]
");
}
#[test]
fn test_git_fetch_tracked() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
// Set up a remote with multiple bookmarks
let remote_path = test_env.env_root().join("remote");
let remote_repo = git::init(remote_path.clone());
add_commit_to_branch(&remote_repo, "main", "message");
add_commit_to_branch(&remote_repo, "feature1", "message");
add_commit_to_branch(&remote_repo, "feature2", "message");
// Initialize jj repo
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Add the remote to the jj repo
work_dir
.run_jj(["git", "remote", "add", "origin", "../remote"])
.success();
// Initially fetch only main and feature1
work_dir
.run_jj(["git", "fetch", "--branch", "main", "--branch", "feature1"])
.success();
// Both should be tracked
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: txqvqkwm fc8f3f42 message
@origin: txqvqkwm fc8f3f42 message
main: kmpysrkw 0130f303 message
@origin: kmpysrkw 0130f303 message
[EOF]
");
// Now untrack feature1
work_dir
.run_jj(["bookmark", "untrack", "feature1"])
.success();
// Verify feature1 is untracked
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: txqvqkwm fc8f3f42 message
feature1@origin: txqvqkwm fc8f3f42 message
main: kmpysrkw 0130f303 message
@origin: kmpysrkw 0130f303 message
[EOF]
");
// Add new commits to all bookmarks on the remote
add_commit_to_branch(&remote_repo, "main", "message");
add_commit_to_branch(&remote_repo, "feature1", "message");
add_commit_to_branch(&remote_repo, "feature2", "message");
// Fetch with --tracked should only update main (which is still tracked)
work_dir.run_jj(["git", "fetch", "--tracked"]).success();
// Main should be updated to the new commit, but feature1 should remain
// unchanged
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: txqvqkwm fc8f3f42 message
feature1@origin: txqvqkwm fc8f3f42 message
main: kmktnoqm 381bf13c (empty) message
@origin: kmktnoqm 381bf13c (empty) message
[EOF]
");
// Now fetch all branches
work_dir.run_jj(["git", "fetch", "--branch", "*"]).success();
// Now feature1@origin gets updated but feature1 stays at old commit
// (untracked), feature2 appears for the first time, and main stays at its
// already-updated commit
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: txqvqkwm fc8f3f42 message
feature1@origin: ksswsvzv 0c0873bb (empty) message
feature2: ruyplonr 13e64e92 (empty) message
@origin: ruyplonr 13e64e92 (empty) message
main: kmktnoqm 381bf13c (empty) message
@origin: kmktnoqm 381bf13c (empty) message
[EOF]
");
}
#[test]
fn test_git_fetch_tracked_no_tracked_bookmarks() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
// Set up a remote with bookmarks
let remote_path = test_env.env_root().join("remote");
let remote_repo = git::init(remote_path.clone());
add_commit_to_branch(&remote_repo, "main", "message");
add_commit_to_branch(&remote_repo, "feature", "message");
// Initialize jj repo
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Add the remote to the jj repo
work_dir
.run_jj(["git", "remote", "add", "origin", "../remote"])
.success();
// Initially fetch bookmarks
work_dir.run_jj(["git", "fetch"]).success();
// Untrack all bookmarks
work_dir.run_jj(["bookmark", "untrack", "*"]).success();
// Fetch with --tracked should indicate nothing changed
let output = work_dir.run_jj(["git", "fetch", "--tracked"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
[EOF]
");
}
#[test]
fn test_git_fetch_tracked_multiple_remotes() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
test_env.add_config("remotes.upstream.auto-track-bookmarks = '*'");
// Set up two remotes with different branches
let origin_path = test_env.env_root().join("origin");
let origin_repo = git::init(origin_path.clone());
add_commit_to_branch(&origin_repo, "main", "origin main commit");
add_commit_to_branch(&origin_repo, "feature1", "origin feature1 commit");
add_commit_to_branch(&origin_repo, "feature2", "origin feature2 commit");
let upstream_path = test_env.env_root().join("upstream");
let upstream_repo = git::init(upstream_path.clone());
add_commit_to_branch(&upstream_repo, "main", "upstream main commit");
add_commit_to_branch(&upstream_repo, "develop", "upstream develop commit");
add_commit_to_branch(&upstream_repo, "hotfix", "upstream hotfix commit");
// Initialize jj repo
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Add both remotes
work_dir
.run_jj(["git", "remote", "add", "origin", "../origin"])
.success();
work_dir
.run_jj(["git", "remote", "add", "upstream", "../upstream"])
.success();
// Initial fetch from both remotes to set up tracking
work_dir.run_jj(["git", "fetch", "--all-remotes"]).success();
// Track different branches from different remotes
work_dir
.run_jj(["bookmark", "track", "feature1", "--remote=origin"])
.success();
work_dir
.run_jj(["bookmark", "track", "develop", "--remote=upstream"])
.success();
// Untrack some branches to test --tracked behavior
work_dir
.run_jj(["bookmark", "untrack", "feature2", "--remote=origin"])
.success();
work_dir
.run_jj(["bookmark", "untrack", "hotfix", "--remote=upstream"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
develop: yzkwtzyq 4217fc8a upstream develop commit
@upstream: yzkwtzyq 4217fc8a upstream develop commit
feature1: ovvpyryn 8a4b3895 origin feature1 commit
@origin: ovvpyryn 8a4b3895 origin feature1 commit
feature2: ysxnuyrn 95a1c2bd origin feature2 commit
feature2@origin: ysxnuyrn 95a1c2bd origin feature2 commit
hotfix: pozyxktk e9e38ee9 upstream hotfix commit
hotfix@upstream: pozyxktk e9e38ee9 upstream hotfix commit
main (conflicted):
+ orvppysl 25f66480 origin main commit
+ nrlvptqt f241ccf9 upstream main commit
@origin (behind by 1 commits): orvppysl 25f66480 origin main commit
@upstream (behind by 1 commits): nrlvptqt f241ccf9 upstream main commit
[EOF]
");
// Add new commits to tracked branches on both remotes
add_commit_to_branch(&origin_repo, "feature1", "new origin feature1 commit");
add_commit_to_branch(&upstream_repo, "develop", "new upstream develop commit");
// Add new commits to untracked branches
add_commit_to_branch(&origin_repo, "feature2", "new origin feature2 commit");
add_commit_to_branch(&upstream_repo, "hotfix", "new upstream hotfix commit");
// Fetch only tracked branches from all remotes
work_dir
.run_jj(["git", "fetch", "--tracked", "--all-remotes"])
.success();
// Only the tracked branches should be updated (feature1 and develop)
// Untracked branches (feature2, hotfix) should remain at old commits
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
develop: kmsovkut 8b5845da (empty) new upstream develop commit
@upstream: kmsovkut 8b5845da (empty) new upstream develop commit
feature1: rmmunkwl d676351d (empty) new origin feature1 commit
@origin: rmmunkwl d676351d (empty) new origin feature1 commit
feature2: ysxnuyrn 95a1c2bd origin feature2 commit
feature2@origin: ysxnuyrn 95a1c2bd origin feature2 commit
hotfix: pozyxktk e9e38ee9 upstream hotfix commit
hotfix@upstream: pozyxktk e9e38ee9 upstream hotfix commit
main (conflicted):
+ orvppysl 25f66480 origin main commit
+ nrlvptqt f241ccf9 upstream main commit
@origin (behind by 1 commits): orvppysl 25f66480 origin main commit
@upstream (behind by 1 commits): nrlvptqt f241ccf9 upstream main commit
[EOF]
");
}
#[test]
fn test_git_fetch_auto_track_bookmarks() {
let test_env = TestEnvironment::default();
let root_dir = test_env.work_dir("");
test_env.add_config(
"
[remotes.origin]
auto-track-bookmarks = 'mine/*'
",
);
root_dir
.run_jj(["git", "init", "--colocate", "origin"])
.success();
let origin_dir = test_env.work_dir("origin");
origin_dir.run_jj(["b", "c", "mine/foo"]).success();
origin_dir.run_jj(["b", "c", "not-mine/foo"]).success();
origin_dir.run_jj(["commit", "-mfoo"]).success();
let output = origin_dir.run_jj(["show", "@-"]);
insta::assert_snapshot!(output, @r"
Commit ID: d7828da83253475bf10c2ae6bd3f0f84bf4604c1
Change ID: qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu
Bookmarks: mine/foo not-mine/foo mine/foo@git not-mine/foo@git
Author : Test User <test.user@example.com> (2001-02-03 08:05:10)
Committer: Test User <test.user@example.com> (2001-02-03 08:05:10)
foo
[EOF]
");
root_dir.run_jj(["git", "init", "repo"]).success();
let repo_dir = test_env.work_dir("repo");
repo_dir
.run_jj(["git", "remote", "add", "origin", "../origin/.git"])
.success();
let output = repo_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: mine/foo@origin [new] tracked
bookmark: not-mine/foo@origin [new] untracked
[EOF]
");
}