forked from mirrors/jj
cli: add jj git push -r
for pushing branches pointing to revset
I think I will find this useful in at least two cases: 1. When you already have a branch pointing to some commit, it's easier to do `jj git push -r xyz` than `jj git push --branch push-xyzxyzyxzxyz`. 2. When you have a stack of changes, it's useful to be able to push all of them at once. I think we should also update the default behavior of `jj git push` to be `jj git push -r 'remote_branches()..@'` or something like that. That removes the ugliness of having a default behavior that the user can't reproduce using flags. I'll leave that change for a separate PR.
This commit is contained in:
parent
9f7180ff55
commit
7f3d07e35f
3 changed files with 119 additions and 21 deletions
|
@ -85,7 +85,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
* `jj git fetch` and `jj git push` will now use the single defined remote even
|
||||
if it is not named "origin".
|
||||
|
||||
* `jj git push` now accepts `--branch` and `--change` arguments together.
|
||||
* `jj git push` now accepts `--branch` and `--change` arguments together.
|
||||
|
||||
* `jj git push` now accepts a `-r/--revisions` flag to specify revisions to
|
||||
push. All branches pointing to any of the specified revisions will be pushed.
|
||||
The flag can be used together with `--branch` and `--change`.
|
||||
|
||||
* `jj` with no subcommand now defaults to `jj log` instead of showing help. This
|
||||
command can be overridden by setting `ui.default-command`.
|
||||
|
|
|
@ -22,8 +22,9 @@ use jujutsu_lib::workspace::Workspace;
|
|||
use maplit::hashset;
|
||||
|
||||
use crate::cli_util::{
|
||||
print_failed_git_export, short_change_hash, short_commit_hash, user_error,
|
||||
user_error_with_hint, CommandError, CommandHelper, RevisionArg, WorkspaceCommandHelper,
|
||||
print_failed_git_export, resolve_multiple_nonempty_revsets, short_change_hash,
|
||||
short_commit_hash, user_error, user_error_with_hint, CommandError, CommandHelper, RevisionArg,
|
||||
WorkspaceCommandHelper,
|
||||
};
|
||||
use crate::commands::make_branch_term;
|
||||
use crate::progress::Progress;
|
||||
|
@ -119,7 +120,7 @@ pub struct GitCloneArgs {
|
|||
/// 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("specific").args(&["branch", "change"]).multiple(true)))]
|
||||
#[command(group(ArgGroup::new("specific").args(&["branch", "change", "revisions"]).multiple(true)))]
|
||||
#[command(group(ArgGroup::new("what").args(&["all", "deleted"]).conflicts_with("specific")))]
|
||||
pub struct GitPushArgs {
|
||||
/// The remote to push to (only named remotes are supported)
|
||||
|
@ -134,6 +135,9 @@ pub struct GitPushArgs {
|
|||
/// Push all deleted branches
|
||||
#[arg(long)]
|
||||
deleted: bool,
|
||||
/// Push branches pointing to these commits
|
||||
#[arg(long, short)]
|
||||
revisions: Vec<RevisionArg>,
|
||||
/// Push this commit by creating a branch based on its change ID (can be
|
||||
/// repeated)
|
||||
#[arg(long)]
|
||||
|
@ -632,6 +636,17 @@ fn cmd_git_push(
|
|||
.iter()
|
||||
.map(|change_str| workspace_command.resolve_single_rev(change_str))
|
||||
.try_collect()?;
|
||||
let revision_commits = resolve_multiple_nonempty_revsets(&args.revisions, &workspace_command)?;
|
||||
fn find_branches_targeting<'a>(
|
||||
view: &'a View,
|
||||
target: &RefTarget,
|
||||
) -> Vec<(&'a String, &'a BranchTarget)> {
|
||||
view.branches()
|
||||
.iter()
|
||||
.filter(|(_, branch_target)| branch_target.local_target.as_ref() == Some(target))
|
||||
.collect()
|
||||
}
|
||||
|
||||
let mut tx = workspace_command.start_transaction("");
|
||||
let tx_description;
|
||||
let mut branch_updates = vec![];
|
||||
|
@ -659,7 +674,7 @@ fn cmd_git_push(
|
|||
if args.deleted { "deleted " } else { "" },
|
||||
&remote
|
||||
);
|
||||
} else if !args.branch.is_empty() || !args.change.is_empty() {
|
||||
} else if !args.branch.is_empty() || !args.change.is_empty() || !args.revisions.is_empty() {
|
||||
for branch_name in &args.branch {
|
||||
if !seen_branches.insert(branch_name.clone()) {
|
||||
continue;
|
||||
|
@ -722,6 +737,31 @@ fn cmd_git_push(
|
|||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut any_revisions_targeted = false;
|
||||
for commit in revision_commits {
|
||||
for (branch_name, branch_target) in
|
||||
find_branches_targeting(repo.view(), &RefTarget::Normal(commit.id().clone()))
|
||||
{
|
||||
any_revisions_targeted = true;
|
||||
if !seen_branches.insert(branch_name.clone()) {
|
||||
continue;
|
||||
}
|
||||
let push_action = classify_branch_push_action(branch_target, &remote);
|
||||
match push_action {
|
||||
BranchPushAction::AlreadyMatches
|
||||
| BranchPushAction::LocalConflicted
|
||||
| BranchPushAction::RemoteConflicted => {}
|
||||
BranchPushAction::Update(update) => {
|
||||
branch_updates.push((branch_name.clone(), update));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !args.revisions.is_empty() && !any_revisions_targeted {
|
||||
return Err(user_error("No branches point to the specified revisions."));
|
||||
}
|
||||
|
||||
tx_description = format!(
|
||||
"push {} to git remote {}",
|
||||
make_branch_term(
|
||||
|
@ -738,18 +778,6 @@ fn cmd_git_push(
|
|||
return Err(user_error("Nothing checked out in this workspace"));
|
||||
}
|
||||
Some(wc_commit) => {
|
||||
fn find_branches_targeting<'a>(
|
||||
view: &'a View,
|
||||
target: &RefTarget,
|
||||
) -> Vec<(&'a String, &'a BranchTarget)> {
|
||||
view.branches()
|
||||
.iter()
|
||||
.filter(|(_, branch_target)| {
|
||||
branch_target.local_target.as_ref() == Some(target)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Search for branches targeting @
|
||||
let mut branches =
|
||||
find_branches_targeting(repo.view(), &RefTarget::Normal(wc_commit.clone()));
|
||||
|
|
|
@ -279,24 +279,90 @@ fn test_git_push_changes() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_push_revisions() {
|
||||
let (test_env, workspace_root) = set_up();
|
||||
test_env.jj_cmd_success(&workspace_root, &["describe", "-m", "foo"]);
|
||||
std::fs::write(workspace_root.join("file"), "contents").unwrap();
|
||||
test_env.jj_cmd_success(&workspace_root, &["new", "-m", "bar"]);
|
||||
test_env.jj_cmd_success(&workspace_root, &["branch", "create", "branch-1"]);
|
||||
std::fs::write(workspace_root.join("file"), "modified").unwrap();
|
||||
test_env.jj_cmd_success(&workspace_root, &["new", "-m", "baz"]);
|
||||
test_env.jj_cmd_success(&workspace_root, &["branch", "create", "branch-2a"]);
|
||||
test_env.jj_cmd_success(&workspace_root, &["branch", "create", "branch-2b"]);
|
||||
std::fs::write(workspace_root.join("file"), "modified again").unwrap();
|
||||
|
||||
// Push an empty set
|
||||
let stderr = test_env.jj_cmd_failure(&workspace_root, &["git", "push", "-r=none()"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Error: Empty revision set
|
||||
"###);
|
||||
// Push a revision with no branches
|
||||
let stderr = test_env.jj_cmd_failure(&workspace_root, &["git", "push", "-r=@--"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Error: No branches point to the specified revisions.
|
||||
"###);
|
||||
// Push a revision with a single branch
|
||||
let stdout = test_env.jj_cmd_success(&workspace_root, &["git", "push", "-r=@-", "--dry-run"]);
|
||||
insta::assert_snapshot!(stdout, @r###"
|
||||
Branch changes to push to origin:
|
||||
Add branch branch-1 to 7decc7932d9c
|
||||
Dry-run requested, not pushing.
|
||||
"###);
|
||||
// Push multiple revisions of which some have branches
|
||||
let stdout = test_env.jj_cmd_success(
|
||||
&workspace_root,
|
||||
&["git", "push", "-r=@--", "-r=@-", "--dry-run"],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @r###"
|
||||
Branch changes to push to origin:
|
||||
Add branch branch-1 to 7decc7932d9c
|
||||
Dry-run requested, not pushing.
|
||||
"###);
|
||||
// Push a revision with a multiple branches
|
||||
let stdout = test_env.jj_cmd_success(&workspace_root, &["git", "push", "-r=@", "--dry-run"]);
|
||||
insta::assert_snapshot!(stdout, @r###"
|
||||
Branch changes to push to origin:
|
||||
Add branch branch-2a to 1b45449e18d0
|
||||
Add branch branch-2b to 1b45449e18d0
|
||||
Dry-run requested, not pushing.
|
||||
"###);
|
||||
// Repeating a commit doesn't result in repeated messages about the branch
|
||||
let stdout = test_env.jj_cmd_success(
|
||||
&workspace_root,
|
||||
&["git", "push", "-r=@-", "-r=@-", "--dry-run"],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @r###"
|
||||
Branch changes to push to origin:
|
||||
Add branch branch-1 to 7decc7932d9c
|
||||
Dry-run requested, not pushing.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_push_mixed() {
|
||||
let (test_env, workspace_root) = set_up();
|
||||
test_env.jj_cmd_success(&workspace_root, &["describe", "-m", "foo"]);
|
||||
std::fs::write(workspace_root.join("file"), "contents").unwrap();
|
||||
test_env.jj_cmd_success(&workspace_root, &["new", "-m", "bar"]);
|
||||
test_env.jj_cmd_success(&workspace_root, &["branch", "create", "my-branch"]);
|
||||
test_env.jj_cmd_success(&workspace_root, &["branch", "create", "branch-1"]);
|
||||
std::fs::write(workspace_root.join("file"), "modified").unwrap();
|
||||
test_env.jj_cmd_success(&workspace_root, &["new", "-m", "baz"]);
|
||||
test_env.jj_cmd_success(&workspace_root, &["branch", "create", "branch-2a"]);
|
||||
test_env.jj_cmd_success(&workspace_root, &["branch", "create", "branch-2b"]);
|
||||
std::fs::write(workspace_root.join("file"), "modified again").unwrap();
|
||||
|
||||
let stdout = test_env.jj_cmd_success(
|
||||
&workspace_root,
|
||||
&["git", "push", "--change=@-", "--branch=my-branch"],
|
||||
&["git", "push", "--change=@--", "--branch=branch-1", "-r=@"],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @r###"
|
||||
Creating branch push-yqosqzytrlsw for revision @-
|
||||
Creating branch push-yqosqzytrlsw for revision @--
|
||||
Branch changes to push to origin:
|
||||
Add branch my-branch to 7decc7932d9c
|
||||
Add branch branch-1 to 7decc7932d9c
|
||||
Add branch push-yqosqzytrlsw to fa16a14170fb
|
||||
Add branch branch-2a to 1b45449e18d0
|
||||
Add branch branch-2b to 1b45449e18d0
|
||||
"###);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue