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
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`
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
any parent-child relationships between them. For example, the entire tree of

View file

@ -180,6 +180,21 @@
"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",
"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
#[derive(clap::Args, Clone, Debug)]
pub struct GitFetchArgs {
/// The remote to fetch from (only named remotes are supported)
#[arg(long)]
remote: Option<String>,
/// The remote to fetch from (only named remotes are supported, can be
/// repeated)
#[arg(long = "remote", value_name = "remote")]
remotes: Vec<String>,
}
/// Create a new repo backed by a clone of a Git repo
@ -238,14 +239,22 @@ fn cmd_git_fetch(
args: &GitFetchArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let remote = if let Some(name) = &args.remote {
name.clone()
let remotes = if args.remotes.is_empty() {
const KEY: &str = "git.fetch";
let config = command.settings().config();
config
.get(KEY)
.or_else(|_| config.get_string(KEY).map(|r| vec![r]))?
} else {
command.settings().config().get("git.fetch")?
args.remotes.clone()
};
let repo = workspace_command.repo();
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!(
"fetch from git remote(s) {}",
remotes.iter().join(",")
));
for remote in remotes {
with_remote_callbacks(ui, |cb| {
git::fetch(
tx.mut_repo(),
@ -256,6 +265,7 @@ fn cmd_git_fetch(
)
})
.map_err(|err| user_error(err.to_string()))?;
}
tx.finish(ui)?;
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]
fn test_git_fetch_nonexistent_remote() {
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");
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###"
Error: No git remote named 'rem1'
Error: No git remote named 'rem2'
"###);
// No remote should have been fetched as part of the failing transaction
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();
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
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"]);
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
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");