ok/jj
1
0
Fork 0
forked from mirrors/jj

Allow jj git push to push multiple branches/changes at once

Also creates short arg `-b` for `--branch`.
This commit is contained in:
Ilya Grigoriev 2022-12-17 14:31:12 -08:00
parent 63854e7b6c
commit fad686f48c
3 changed files with 113 additions and 71 deletions

View file

@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `jj log --summary --patch` now shows both summary and diff outputs.
* `jj git push` now accepts multiple `--branch`/`--change` arguments
### Fixed bugs
* When sharing the working copy with a Git repo, we used to forget to export
@ -28,6 +30,7 @@ Thanks to the people who made this release happen!
* Martin von Zweigbergk (@martinvonz)
* Danny Hooper (hooper@google.com)
* Yuya Nishihara (@yuja)
* Ilya Grigoriev (@ilyagr)
## [0.6.1] - 2022-12-05

View file

@ -919,24 +919,25 @@ struct GitCloneArgs {
/// Push to a Git remote
///
/// By default, pushes any branches pointing to `@`, or `@-` if no branches
/// point to `@`. Use `--branch` to push a specific branch. Use `--all` to push
/// all branches. Use `--change` to generate a branch name based on a specific
/// commit's change ID.
/// point to `@`. Use `--branch` to push specific branches. Use `--all` to push
/// all branches. Use `--change` to generate branch names based on the change
/// IDs of specific commits.
#[derive(clap::Args, Clone, Debug)]
#[command(group(ArgGroup::new("what").args(&["branch", "all", "change"])))]
struct GitPushArgs {
/// The remote to push to (only named remotes are supported)
#[arg(long, default_value = "origin")]
remote: String,
/// Push only this branch
#[arg(long)]
branch: Option<String>,
/// Push only this branch (can be repeated)
#[arg(long, short)]
branch: Vec<String>,
/// Push all branches
#[arg(long)]
all: bool,
/// Push this commit by creating a branch based on its change ID
/// Push this commit by creating a branch based on its change ID (can be
/// repeated)
#[arg(long)]
change: Option<RevisionArg>,
change: Vec<RevisionArg>,
/// Only display what will change on the remote
#[arg(long)]
dry_run: bool,
@ -2783,6 +2784,18 @@ fn is_fast_forward(repo: RepoRef, branch_name: &str, new_target_id: &CommitId) -
}
}
fn make_branch_term(branch_names: &[impl AsRef<str>]) -> String {
match branch_names {
[branch_name] => format!("branch {}", branch_name.as_ref()),
branch_names => {
format!(
"branches {}",
branch_names.iter().map(AsRef::as_ref).join(", ")
)
}
}
}
fn cmd_branch(
ui: &mut Ui,
command: &CommandHelper,
@ -2817,18 +2830,6 @@ fn cmd_branch(
Ok(matching_branches)
}
fn make_branch_term(branch_names: &[impl AsRef<str>]) -> String {
match branch_names {
[branch_name] => format!("branch {}", branch_name.as_ref()),
branch_names => {
format!(
"branches {}",
branch_names.iter().map(AsRef::as_ref).join(", ")
)
}
}
}
match subcommand {
BranchSubcommand::Create { revision, names } => {
let branch_names: Vec<&str> = names
@ -3867,61 +3868,76 @@ fn cmd_git_push(
let mut tx;
let mut branch_updates = vec![];
if let Some(branch_name) = &args.branch {
if let Some(update) = branch_updates_for_push(
workspace_command.repo().as_repo_ref(),
&args.remote,
branch_name,
)? {
branch_updates.push((branch_name.clone(), update));
} else {
writeln!(
ui,
"Branch {}@{} already matches {}",
branch_name, &args.remote, branch_name
)?;
}
tx = workspace_command.start_transaction(&format!(
"push branch {branch_name} to git remote {}",
&args.remote
));
} else if let Some(change_str) = &args.change {
let commit = workspace_command.resolve_single_rev(change_str)?;
let branch_name = format!(
"{}{}",
ui.settings().push_branch_prefix(),
commit.change_id().hex()
);
if workspace_command
.repo()
.view()
.get_local_branch(&branch_name)
.is_none()
{
writeln!(
ui,
"Creating branch {} for revision {}",
if !args.branch.is_empty() {
for branch_name in &args.branch {
if let Some(update) = branch_updates_for_push(
workspace_command.repo().as_repo_ref(),
&args.remote,
branch_name,
change_str.deref()
)?;
)? {
branch_updates.push((branch_name.clone(), update));
} else {
writeln!(
ui,
"Branch {}@{} already matches {}",
branch_name, &args.remote, branch_name
)?;
}
}
tx = workspace_command.start_transaction(&format!(
"push change {} to git remote {}",
commit.change_id().hex(),
"push {} to git remote {}",
make_branch_term(&args.branch),
&args.remote
));
tx.mut_repo()
.set_local_branch(branch_name.clone(), RefTarget::Normal(commit.id().clone()));
if let Some(update) =
branch_updates_for_push(tx.mut_repo().as_repo_ref(), &args.remote, &branch_name)?
{
branch_updates.push((branch_name.clone(), update));
} else {
writeln!(
ui,
"Branch {}@{} already matches {}",
branch_name, &args.remote, branch_name
)?;
} else if !args.change.is_empty() {
// TODO: Allow specifying --branch and --change at the same time
let commits: Vec<_> = args
.change
.iter()
.map(|change_str| workspace_command.resolve_single_rev(change_str))
.try_collect()?;
tx = workspace_command.start_transaction(&format!(
"push {} {} to git remote {}",
if commits.len() > 1 {
"changes"
} else {
"change"
},
commits.iter().map(|c| c.change_id().hex()).join(", "),
&args.remote
));
for (change_str, commit) in std::iter::zip(args.change.iter(), commits) {
let branch_name = format!(
"{}{}",
ui.settings().push_branch_prefix(),
commit.change_id().hex()
);
if workspace_command
.repo()
.view()
.get_local_branch(&branch_name)
.is_none()
{
writeln!(
ui,
"Creating branch {} for revision {}",
branch_name,
change_str.deref()
)?;
}
tx.mut_repo()
.set_local_branch(branch_name.clone(), RefTarget::Normal(commit.id().clone()));
if let Some(update) =
branch_updates_for_push(tx.mut_repo().as_repo_ref(), &args.remote, &branch_name)?
{
branch_updates.push((branch_name.clone(), update));
} else {
writeln!(
ui,
"Branch {}@{} already matches {}",
branch_name, &args.remote, branch_name
)?;
}
}
} else if args.all {
// TODO: Is it useful to warn about conflicted branches?

View file

@ -130,7 +130,7 @@ fn test_git_push_no_current_branch() {
}
#[test]
fn test_git_push_all() {
fn test_git_push_multiple() {
let (test_env, workspace_root) = set_up();
test_env.jj_cmd_success(&workspace_root, &["branch", "delete", "branch1"]);
test_env.jj_cmd_success(
@ -157,6 +157,17 @@ fn test_git_push_all() {
Add branch my-branch to afc3e612e744
Dry-run requested, not pushing.
"###);
// Dry run requesting two specific branches
let stdout = test_env.jj_cmd_success(
&workspace_root,
&["git", "push", "-b=branch1", "-b=my-branch", "--dry-run"],
);
insta::assert_snapshot!(stdout, @r###"
Branch changes to push to origin:
Delete branch branch1 from 828a683493c6
Add branch my-branch to afc3e612e744
Dry-run requested, not pushing.
"###);
let stdout = test_env.jj_cmd_success(&workspace_root, &["git", "push", "--all"]);
insta::assert_snapshot!(stdout, @r###"
Branch changes to push to origin:
@ -191,6 +202,18 @@ fn test_git_push_changes() {
Branch changes to push to origin:
Add branch push-<CHANGE_ID> to ccebc2439094
"###);
// test pushing two changes at once
std::fs::write(workspace_root.join("file"), "modified2").unwrap();
let stdout = test_env.jj_cmd_success(
&workspace_root,
&["git", "push", "--change", "@", "--change", "@-"],
);
insta::assert_snapshot!(replace_changeid(&stdout), @r###"
Creating branch push-<CHANGE_ID> for revision @-
Branch changes to push to origin:
Force branch push-<CHANGE_ID> from ccebc2439094 to 1624f122b2b1
Add branch push-<CHANGE_ID> to 0a736fed65c0
"###);
}
#[test]