forked from mirrors/jj
git fetch: accept several remotes
The "--remote" option can be repeated, and the "git.fetch" key is now a list.
This commit is contained in:
parent
4550b9c481
commit
af9471e65c
4 changed files with 89 additions and 22 deletions
|
@ -74,9 +74,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
and `remote_needle` as optional arguments and matches just the branches whose
|
and `remote_needle` as optional arguments and matches just the branches whose
|
||||||
name contains `branch_needle` and remote contains `remote_needle`.
|
name contains `branch_needle` and remote contains `remote_needle`.
|
||||||
|
|
||||||
|
* `jj git fetch` accepts repeated `--remote` arguments.
|
||||||
|
|
||||||
* Default remotes can be configured for the `jj git fetch` and `jj git push`
|
* Default remotes can be configured for the `jj git fetch` and `jj git push`
|
||||||
operations ("origin" by default) using the `git.fetch` and `git.push`
|
operations ("origin" by default) using the `git.fetch` and `git.push`
|
||||||
configuration entries.
|
configuration entries. `git.fetch` can be a list if multiple remotes must
|
||||||
|
be fetched from.
|
||||||
|
|
||||||
* `jj duplicate` can now duplicate multiple changes in one go. This preserves
|
* `jj duplicate` can now duplicate multiple changes in one go. This preserves
|
||||||
any parent-child relationships between them. For example, the entire tree of
|
any parent-child relationships between them. For example, the entire tree of
|
||||||
|
|
|
@ -180,6 +180,21 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether jj creates a local branch with the same name when it imports a remote-tracking branch from git. See https://github.com/martinvonz/jj/blob/main/docs/config.md#automatic-local-branch-creation",
|
"description": "Whether jj creates a local branch with the same name when it imports a remote-tracking branch from git. See https://github.com/martinvonz/jj/blob/main/docs/config.md#automatic-local-branch-creation",
|
||||||
"default": true
|
"default": true
|
||||||
|
},
|
||||||
|
"fetch": {
|
||||||
|
"description": "The remote(s) from which commits are fetched",
|
||||||
|
"default": "origin",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -85,9 +85,10 @@ pub struct GitRemoteListArgs {}
|
||||||
/// Fetch from a Git remote
|
/// Fetch from a Git remote
|
||||||
#[derive(clap::Args, Clone, Debug)]
|
#[derive(clap::Args, Clone, Debug)]
|
||||||
pub struct GitFetchArgs {
|
pub struct GitFetchArgs {
|
||||||
/// The remote to fetch from (only named remotes are supported)
|
/// The remote to fetch from (only named remotes are supported, can be
|
||||||
#[arg(long)]
|
/// repeated)
|
||||||
remote: Option<String>,
|
#[arg(long = "remote", value_name = "remote")]
|
||||||
|
remotes: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new repo backed by a clone of a Git repo
|
/// Create a new repo backed by a clone of a Git repo
|
||||||
|
@ -238,24 +239,33 @@ fn cmd_git_fetch(
|
||||||
args: &GitFetchArgs,
|
args: &GitFetchArgs,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
let mut workspace_command = command.workspace_helper(ui)?;
|
let mut workspace_command = command.workspace_helper(ui)?;
|
||||||
let remote = if let Some(name) = &args.remote {
|
let remotes = if args.remotes.is_empty() {
|
||||||
name.clone()
|
const KEY: &str = "git.fetch";
|
||||||
|
let config = command.settings().config();
|
||||||
|
config
|
||||||
|
.get(KEY)
|
||||||
|
.or_else(|_| config.get_string(KEY).map(|r| vec![r]))?
|
||||||
} else {
|
} else {
|
||||||
command.settings().config().get("git.fetch")?
|
args.remotes.clone()
|
||||||
};
|
};
|
||||||
let repo = workspace_command.repo();
|
let repo = workspace_command.repo();
|
||||||
let git_repo = get_git_repo(repo.store())?;
|
let git_repo = get_git_repo(repo.store())?;
|
||||||
let mut tx = workspace_command.start_transaction(&format!("fetch from git remote {}", &remote));
|
let mut tx = workspace_command.start_transaction(&format!(
|
||||||
with_remote_callbacks(ui, |cb| {
|
"fetch from git remote(s) {}",
|
||||||
git::fetch(
|
remotes.iter().join(",")
|
||||||
tx.mut_repo(),
|
));
|
||||||
&git_repo,
|
for remote in remotes {
|
||||||
&remote,
|
with_remote_callbacks(ui, |cb| {
|
||||||
cb,
|
git::fetch(
|
||||||
&command.settings().git_settings(),
|
tx.mut_repo(),
|
||||||
)
|
&git_repo,
|
||||||
})
|
&remote,
|
||||||
.map_err(|err| user_error(err.to_string()))?;
|
cb,
|
||||||
|
&command.settings().git_settings(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map_err(|err| user_error(err.to_string()))?;
|
||||||
|
}
|
||||||
tx.finish(ui)?;
|
tx.finish(ui)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,15 +57,53 @@ fn test_git_fetch_single_remote_from_config() {
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_git_fetch_multiple_remotes() {
|
||||||
|
let test_env = TestEnvironment::default();
|
||||||
|
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
|
||||||
|
let repo_path = test_env.env_root().join("repo");
|
||||||
|
add_git_remote(&test_env, &repo_path, "rem1");
|
||||||
|
add_git_remote(&test_env, &repo_path, "rem2");
|
||||||
|
|
||||||
|
test_env.jj_cmd_success(
|
||||||
|
&repo_path,
|
||||||
|
&["git", "fetch", "--remote", "rem1", "--remote", "rem2"],
|
||||||
|
);
|
||||||
|
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
|
||||||
|
rem1: 9f01a0e04879 message
|
||||||
|
rem2: 9f01a0e04879 message
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_git_fetch_multiple_remotes_from_config() {
|
||||||
|
let test_env = TestEnvironment::default();
|
||||||
|
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
|
||||||
|
let repo_path = test_env.env_root().join("repo");
|
||||||
|
add_git_remote(&test_env, &repo_path, "rem1");
|
||||||
|
add_git_remote(&test_env, &repo_path, "rem2");
|
||||||
|
test_env.add_config(r#"git.fetch = ["rem1", "rem2"]"#);
|
||||||
|
|
||||||
|
test_env.jj_cmd_success(&repo_path, &["git", "fetch"]);
|
||||||
|
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
|
||||||
|
rem1: 9f01a0e04879 message
|
||||||
|
rem2: 9f01a0e04879 message
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_git_fetch_nonexistent_remote() {
|
fn test_git_fetch_nonexistent_remote() {
|
||||||
let test_env = TestEnvironment::default();
|
let test_env = TestEnvironment::default();
|
||||||
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
|
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
|
||||||
let repo_path = test_env.env_root().join("repo");
|
let repo_path = test_env.env_root().join("repo");
|
||||||
|
add_git_remote(&test_env, &repo_path, "rem1");
|
||||||
|
|
||||||
let stderr = &test_env.jj_cmd_failure(&repo_path, &["git", "fetch", "--remote", "rem1"]);
|
let stderr = &test_env.jj_cmd_failure(
|
||||||
|
&repo_path,
|
||||||
|
&["git", "fetch", "--remote", "rem1", "--remote", "rem2"],
|
||||||
|
);
|
||||||
insta::assert_snapshot!(stderr, @r###"
|
insta::assert_snapshot!(stderr, @r###"
|
||||||
Error: No git remote named 'rem1'
|
Error: No git remote named 'rem2'
|
||||||
"###);
|
"###);
|
||||||
// No remote should have been fetched as part of the failing transaction
|
// No remote should have been fetched as part of the failing transaction
|
||||||
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
|
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
|
||||||
|
@ -76,11 +114,12 @@ fn test_git_fetch_nonexistent_remote_from_config() {
|
||||||
let test_env = TestEnvironment::default();
|
let test_env = TestEnvironment::default();
|
||||||
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
|
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
|
||||||
let repo_path = test_env.env_root().join("repo");
|
let repo_path = test_env.env_root().join("repo");
|
||||||
test_env.add_config(r#"git.fetch = "rem1""#);
|
add_git_remote(&test_env, &repo_path, "rem1");
|
||||||
|
test_env.add_config(r#"git.fetch = ["rem1", "rem2"]"#);
|
||||||
|
|
||||||
let stderr = &test_env.jj_cmd_failure(&repo_path, &["git", "fetch"]);
|
let stderr = &test_env.jj_cmd_failure(&repo_path, &["git", "fetch"]);
|
||||||
insta::assert_snapshot!(stderr, @r###"
|
insta::assert_snapshot!(stderr, @r###"
|
||||||
Error: No git remote named 'rem1'
|
Error: No git remote named 'rem2'
|
||||||
"###);
|
"###);
|
||||||
// No remote should have been fetched as part of the failing transaction
|
// No remote should have been fetched as part of the failing transaction
|
||||||
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
|
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
|
||||||
|
|
Loading…
Reference in a new issue