ok/jj
1
0
Fork 0
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:
Samuel Tardieu 2023-02-02 19:31:11 +01:00
parent 4550b9c481
commit af9471e65c
4 changed files with 89 additions and 22 deletions

View file

@ -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

View file

@ -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"
}
}
]
} }
} }
}, },

View file

@ -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(())
} }

View file

@ -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), @"");