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

cli: make jj git push push all branches by default

It's annoying to have to add `--branch main` every time I push to
GitHub.

Maybe we should make it push only the current branch by default, but
we don't even have a concept of a current branch yet...
This commit is contained in:
Martin von Zweigbergk 2021-09-11 17:09:48 -07:00
parent 0bc42c0066
commit cea3c1537a
2 changed files with 105 additions and 75 deletions

View file

@ -108,12 +108,15 @@ fn find_pair_to_remove(
None
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BranchPushUpdate {
pub old_target: Option<CommitId>,
pub new_target: Option<CommitId>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BranchPushAction {
Update {
old_target: Option<CommitId>,
new_target: Option<CommitId>,
},
Update(BranchPushUpdate),
AlreadyMatches,
LocalConflicted,
RemoteConflicted,
@ -134,19 +137,19 @@ pub fn classify_branch_push_action(
(_, Some(RefTarget::Conflict { .. })) => BranchPushAction::LocalConflicted,
(Some(RefTarget::Conflict { .. }), _) => BranchPushAction::RemoteConflicted,
(Some(RefTarget::Normal(old_target)), Some(RefTarget::Normal(new_target))) => {
BranchPushAction::Update {
BranchPushAction::Update(BranchPushUpdate {
old_target: Some(old_target.clone()),
new_target: Some(new_target.clone()),
}
})
}
(Some(RefTarget::Normal(old_target)), None) => BranchPushAction::Update {
(Some(RefTarget::Normal(old_target)), None) => BranchPushAction::Update(BranchPushUpdate {
old_target: Some(old_target.clone()),
new_target: None,
},
(None, Some(RefTarget::Normal(new_target))) => BranchPushAction::Update {
}),
(None, Some(RefTarget::Normal(new_target))) => BranchPushAction::Update(BranchPushUpdate {
old_target: None,
new_target: Some(new_target.clone()),
},
}),
(None, None) => {
panic!("Unexpected branch doesn't exist anywhere")
}
@ -183,10 +186,10 @@ mod tests {
};
assert_eq!(
classify_branch_push_action(&branch, "origin"),
BranchPushAction::Update {
BranchPushAction::Update(BranchPushUpdate {
old_target: None,
new_target: Some(commit_id1),
}
})
);
}
@ -201,10 +204,10 @@ mod tests {
};
assert_eq!(
classify_branch_push_action(&branch, "origin"),
BranchPushAction::Update {
BranchPushAction::Update(BranchPushUpdate {
old_target: Some(commit_id1),
new_target: None,
}
})
);
}
@ -220,10 +223,10 @@ mod tests {
};
assert_eq!(
classify_branch_push_action(&branch, "origin"),
BranchPushAction::Update {
BranchPushAction::Update(BranchPushUpdate {
old_target: Some(commit_id1),
new_target: Some(commit_id2),
}
})
);
}

View file

@ -1260,13 +1260,17 @@ https://github.com/martinvonz/jj/blob/main/docs/git-comparison.md.\
)
.subcommand(
SubCommand::with_name("push")
.about("Push a branch to a Git remote")
.about("Push to a Git remote")
.long_about(
"Push to a Git remote
By default, all branches are pushed. Use `--branch` if you want to push only one branch.",
)
.arg(
Arg::with_name("branch")
.long("branch")
.takes_value(true)
.required(true)
.help("The name of the branch to push"),
.help("Push only this branch"),
)
.arg(
Arg::with_name("remote")
@ -3296,69 +3300,92 @@ fn cmd_git_push(
) -> Result<(), CommandError> {
let repo_command = command.repo_helper(ui)?;
let repo = repo_command.repo();
let branch_name = cmd_matches.value_of("branch").unwrap();
let remote_name = cmd_matches.value_of("remote").unwrap();
let maybe_branch_target = repo.view().get_branch(branch_name);
if maybe_branch_target.is_none() {
return Err(CommandError::UserError(format!(
"Branch {} doesn't exist",
branch_name
)));
}
let branch_target = maybe_branch_target.unwrap();
let push_action = classify_branch_push_action(branch_target, remote_name);
let mut ref_updates = vec![];
match push_action {
BranchPushAction::AlreadyMatches => {
writeln!(
ui,
"Branch {}@{} already matches {}",
branch_name, remote_name, branch_name
)?;
return Ok(());
}
BranchPushAction::LocalConflicted => {
let mut branch_updates = HashMap::new();
if let Some(branch_name) = cmd_matches.value_of("branch") {
let maybe_branch_target = repo.view().get_branch(branch_name);
if maybe_branch_target.is_none() {
return Err(CommandError::UserError(format!(
"Branch {} is conflicted",
"Branch {} doesn't exist",
branch_name
)));
}
BranchPushAction::RemoteConflicted => {
return Err(CommandError::UserError(format!(
"Branch {}@{} is conflicted",
branch_name, remote_name
)));
}
BranchPushAction::Update {
old_target,
new_target,
} => {
let qualified_name = format!("refs/heads/{}", branch_name);
if let Some(new_target) = new_target {
let new_target_commit = repo.store().get_commit(&new_target)?;
if new_target_commit.is_open() {
return Err(CommandError::UserError(
"Won't push open commit".to_string(),
));
}
let force = match old_target {
None => false,
Some(old_target) => !repo.index().is_ancestor(&old_target, &new_target),
};
ref_updates.push(GitRefUpdate {
qualified_name,
force,
new_target: Some(new_target),
});
} else {
ref_updates.push(GitRefUpdate {
qualified_name,
force: false,
new_target: None,
});
let branch_target = maybe_branch_target.unwrap();
let push_action = classify_branch_push_action(branch_target, remote_name);
match push_action {
BranchPushAction::AlreadyMatches => {
writeln!(
ui,
"Branch {}@{} already matches {}",
branch_name, remote_name, branch_name
)?;
return Ok(());
}
BranchPushAction::LocalConflicted => {
return Err(CommandError::UserError(format!(
"Branch {} is conflicted",
branch_name
)));
}
BranchPushAction::RemoteConflicted => {
return Err(CommandError::UserError(format!(
"Branch {}@{} is conflicted",
branch_name, remote_name
)));
}
BranchPushAction::Update(update) => {
if let Some(new_target) = &update.new_target {
let new_target_commit = repo.store().get_commit(new_target)?;
if new_target_commit.is_open() {
return Err(CommandError::UserError(
"Won't push open commit".to_string(),
));
}
}
branch_updates.insert(branch_name, update);
}
}
} else {
// TODO: Is it useful to warn about conflicted branches?
for (branch_name, branch_target) in repo.view().branches() {
let push_action = classify_branch_push_action(branch_target, remote_name);
match push_action {
BranchPushAction::AlreadyMatches => {}
BranchPushAction::LocalConflicted => {}
BranchPushAction::RemoteConflicted => {}
BranchPushAction::Update(update) => {
branch_updates.insert(branch_name, update);
}
}
}
}
if branch_updates.is_empty() {
writeln!(ui, "Nothing changed.")?;
return Ok(());
}
let mut ref_updates = vec![];
for (branch_name, update) in branch_updates {
let qualified_name = format!("refs/heads/{}", branch_name);
if let Some(new_target) = update.new_target {
let force = match update.old_target {
None => false,
Some(old_target) => !repo.index().is_ancestor(&old_target, &new_target),
};
ref_updates.push(GitRefUpdate {
qualified_name,
force,
new_target: Some(new_target),
});
} else {
ref_updates.push(GitRefUpdate {
qualified_name,
force: false,
new_target: None,
});
}
}