mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-02 18:01:05 +00:00
refactor: extract branch commands into commands/branch module
This commit is contained in:
parent
8f8fd7c89a
commit
3d09f675bd
2 changed files with 330 additions and 314 deletions
324
src/commands/branch.rs
Normal file
324
src/commands/branch.rs
Normal file
|
@ -0,0 +1,324 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use clap::builder::NonEmptyStringValueParser;
|
||||
use itertools::Itertools;
|
||||
use jujutsu_lib::backend::{CommitId, ObjectId};
|
||||
use jujutsu_lib::op_store::RefTarget;
|
||||
use jujutsu_lib::repo::RepoRef;
|
||||
use jujutsu_lib::view::View;
|
||||
|
||||
use crate::cli_util::{
|
||||
user_error, user_error_with_hint, CommandError, CommandHelper, RevisionArg,
|
||||
WorkspaceCommandHelper,
|
||||
};
|
||||
use crate::commands::make_branch_term;
|
||||
use crate::formatter::Formatter;
|
||||
use crate::ui::Ui;
|
||||
|
||||
/// Manage branches.
|
||||
///
|
||||
/// For information about branches, see
|
||||
/// https://github.com/martinvonz/jj/blob/main/docs/branches.md.
|
||||
#[derive(clap::Subcommand, Clone, Debug)]
|
||||
pub enum BranchSubcommand {
|
||||
/// Create a new branch.
|
||||
#[command(visible_alias("c"))]
|
||||
Create {
|
||||
/// The branch's target revision.
|
||||
#[arg(long, short)]
|
||||
revision: Option<RevisionArg>,
|
||||
|
||||
/// The branches to create.
|
||||
#[arg(required = true, value_parser=NonEmptyStringValueParser::new())]
|
||||
names: Vec<String>,
|
||||
},
|
||||
|
||||
/// Delete an existing branch and propagate the deletion to remotes on the
|
||||
/// next push.
|
||||
#[command(visible_alias("d"))]
|
||||
Delete {
|
||||
/// The branches to delete.
|
||||
#[arg(required = true)]
|
||||
names: Vec<String>,
|
||||
},
|
||||
|
||||
/// Forget everything about a branch, including its local and remote
|
||||
/// targets.
|
||||
///
|
||||
/// A forgotten branch will not impact remotes on future pushes. It will be
|
||||
/// recreated on future pulls if it still exists in the remote.
|
||||
#[command(visible_alias("f"))]
|
||||
Forget {
|
||||
/// The branches to forget.
|
||||
#[arg(required_unless_present_any(&["glob"]))]
|
||||
names: Vec<String>,
|
||||
|
||||
/// A glob pattern indicating branches to forget.
|
||||
#[arg(long)]
|
||||
glob: Vec<String>,
|
||||
},
|
||||
|
||||
/// List branches and their targets
|
||||
///
|
||||
/// A remote branch will be included only if its target is different from
|
||||
/// the local target. For a conflicted branch (both local and remote), old
|
||||
/// target revisions are preceded by a "-" and new target revisions are
|
||||
/// preceded by a "+". For information about branches, see
|
||||
/// https://github.com/martinvonz/jj/blob/main/docs/branches.md.
|
||||
#[command(visible_alias("l"))]
|
||||
List,
|
||||
|
||||
/// Update a given branch to point to a certain commit.
|
||||
#[command(visible_alias("s"))]
|
||||
Set {
|
||||
/// The branch's target revision.
|
||||
#[arg(long, short)]
|
||||
revision: Option<RevisionArg>,
|
||||
|
||||
/// Allow moving the branch backwards or sideways.
|
||||
#[arg(long, short = 'B')]
|
||||
allow_backwards: bool,
|
||||
|
||||
/// The branches to update.
|
||||
#[arg(required = true)]
|
||||
names: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn cmd_branch(
|
||||
ui: &mut Ui,
|
||||
command: &CommandHelper,
|
||||
subcommand: &BranchSubcommand,
|
||||
) -> Result<(), CommandError> {
|
||||
let mut workspace_command = command.workspace_helper(ui)?;
|
||||
let view = workspace_command.repo().view();
|
||||
fn validate_branch_names_exist<'a>(
|
||||
view: &'a View,
|
||||
names: &'a [String],
|
||||
) -> Result<(), CommandError> {
|
||||
for branch_name in names {
|
||||
if view.get_local_branch(branch_name).is_none() {
|
||||
return Err(user_error(format!("No such branch: {branch_name}")));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_globs(view: &View, globs: &[String]) -> Result<Vec<String>, CommandError> {
|
||||
let globs: Vec<glob::Pattern> = globs
|
||||
.iter()
|
||||
.map(|glob| glob::Pattern::new(glob))
|
||||
.try_collect()?;
|
||||
let matching_branches = view
|
||||
.branches()
|
||||
.iter()
|
||||
.map(|(branch_name, _branch_target)| branch_name)
|
||||
.filter(|branch_name| globs.iter().any(|glob| glob.matches(branch_name)))
|
||||
.cloned()
|
||||
.collect();
|
||||
Ok(matching_branches)
|
||||
}
|
||||
|
||||
match subcommand {
|
||||
BranchSubcommand::Create { revision, names } => {
|
||||
let branch_names: Vec<&str> = names
|
||||
.iter()
|
||||
.map(|branch_name| match view.get_local_branch(branch_name) {
|
||||
Some(_) => Err(user_error_with_hint(
|
||||
format!("Branch already exists: {branch_name}"),
|
||||
"Use `jj branch set` to update it.",
|
||||
)),
|
||||
None => Ok(branch_name.as_str()),
|
||||
})
|
||||
.try_collect()?;
|
||||
|
||||
if branch_names.len() > 1 {
|
||||
writeln!(
|
||||
ui.warning(),
|
||||
"warning: Creating multiple branches ({}).",
|
||||
branch_names.len()
|
||||
)?;
|
||||
}
|
||||
|
||||
let target_commit =
|
||||
workspace_command.resolve_single_rev(revision.as_deref().unwrap_or("@"))?;
|
||||
let mut tx = workspace_command.start_transaction(&format!(
|
||||
"create {} pointing to commit {}",
|
||||
make_branch_term(&branch_names),
|
||||
target_commit.id().hex()
|
||||
));
|
||||
for branch_name in branch_names {
|
||||
tx.mut_repo().set_local_branch(
|
||||
branch_name.to_string(),
|
||||
RefTarget::Normal(target_commit.id().clone()),
|
||||
);
|
||||
}
|
||||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
||||
BranchSubcommand::Set {
|
||||
revision,
|
||||
allow_backwards,
|
||||
names: branch_names,
|
||||
} => {
|
||||
if branch_names.len() > 1 {
|
||||
writeln!(
|
||||
ui.warning(),
|
||||
"warning: Updating multiple branches ({}).",
|
||||
branch_names.len()
|
||||
)?;
|
||||
}
|
||||
|
||||
let target_commit =
|
||||
workspace_command.resolve_single_rev(revision.as_deref().unwrap_or("@"))?;
|
||||
if !allow_backwards
|
||||
&& !branch_names.iter().all(|branch_name| {
|
||||
is_fast_forward(
|
||||
workspace_command.repo().as_repo_ref(),
|
||||
branch_name,
|
||||
target_commit.id(),
|
||||
)
|
||||
})
|
||||
{
|
||||
return Err(user_error_with_hint(
|
||||
"Refusing to move branch backwards or sideways.",
|
||||
"Use --allow-backwards to allow it.",
|
||||
));
|
||||
}
|
||||
let mut tx = workspace_command.start_transaction(&format!(
|
||||
"point {} to commit {}",
|
||||
make_branch_term(branch_names),
|
||||
target_commit.id().hex()
|
||||
));
|
||||
for branch_name in branch_names {
|
||||
tx.mut_repo().set_local_branch(
|
||||
branch_name.to_string(),
|
||||
RefTarget::Normal(target_commit.id().clone()),
|
||||
);
|
||||
}
|
||||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
||||
BranchSubcommand::Delete { names } => {
|
||||
validate_branch_names_exist(view, names)?;
|
||||
let mut tx =
|
||||
workspace_command.start_transaction(&format!("delete {}", make_branch_term(names)));
|
||||
for branch_name in names {
|
||||
tx.mut_repo().remove_local_branch(branch_name);
|
||||
}
|
||||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
||||
BranchSubcommand::Forget { names, glob } => {
|
||||
validate_branch_names_exist(view, names)?;
|
||||
let globbed_names = find_globs(view, glob)?;
|
||||
let names: BTreeSet<String> = names.iter().cloned().chain(globbed_names).collect();
|
||||
let branch_term = make_branch_term(names.iter().collect_vec().as_slice());
|
||||
let mut tx = workspace_command.start_transaction(&format!("forget {branch_term}"));
|
||||
for branch_name in names {
|
||||
tx.mut_repo().remove_branch(&branch_name);
|
||||
}
|
||||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
||||
BranchSubcommand::List => {
|
||||
list_branches(ui, command, &workspace_command)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_branches(
|
||||
ui: &mut Ui,
|
||||
_command: &CommandHelper,
|
||||
workspace_command: &WorkspaceCommandHelper,
|
||||
) -> Result<(), CommandError> {
|
||||
let repo = workspace_command.repo();
|
||||
|
||||
let print_branch_target =
|
||||
|formatter: &mut dyn Formatter, target: Option<&RefTarget>| -> Result<(), CommandError> {
|
||||
match target {
|
||||
Some(RefTarget::Normal(id)) => {
|
||||
write!(formatter, ": ")?;
|
||||
let commit = repo.store().get_commit(id)?;
|
||||
workspace_command.write_commit_summary(formatter, &commit)?;
|
||||
writeln!(formatter)?;
|
||||
}
|
||||
Some(RefTarget::Conflict { adds, removes }) => {
|
||||
write!(formatter, " ")?;
|
||||
write!(formatter.labeled("conflict"), "(conflicted)")?;
|
||||
writeln!(formatter, ":")?;
|
||||
for id in removes {
|
||||
let commit = repo.store().get_commit(id)?;
|
||||
write!(formatter, " - ")?;
|
||||
workspace_command.write_commit_summary(formatter, &commit)?;
|
||||
writeln!(formatter)?;
|
||||
}
|
||||
for id in adds {
|
||||
let commit = repo.store().get_commit(id)?;
|
||||
write!(formatter, " + ")?;
|
||||
workspace_command.write_commit_summary(formatter, &commit)?;
|
||||
writeln!(formatter)?;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
writeln!(formatter, " (deleted)")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
let formatter = formatter.as_mut();
|
||||
let index = repo.index();
|
||||
for (name, branch_target) in repo.view().branches() {
|
||||
write!(formatter.labeled("branch"), "{name}")?;
|
||||
print_branch_target(formatter, branch_target.local_target.as_ref())?;
|
||||
|
||||
for (remote, remote_target) in branch_target
|
||||
.remote_targets
|
||||
.iter()
|
||||
.sorted_by_key(|(name, _target)| name.to_owned())
|
||||
{
|
||||
if Some(remote_target) == branch_target.local_target.as_ref() {
|
||||
continue;
|
||||
}
|
||||
write!(formatter, " ")?;
|
||||
write!(formatter.labeled("branch"), "@{remote}")?;
|
||||
if let Some(local_target) = branch_target.local_target.as_ref() {
|
||||
let remote_ahead_count = index
|
||||
.walk_revs(&remote_target.adds(), &local_target.adds())
|
||||
.count();
|
||||
let local_ahead_count = index
|
||||
.walk_revs(&local_target.adds(), &remote_target.adds())
|
||||
.count();
|
||||
if remote_ahead_count != 0 && local_ahead_count == 0 {
|
||||
write!(formatter, " (ahead by {remote_ahead_count} commits)")?;
|
||||
} else if remote_ahead_count == 0 && local_ahead_count != 0 {
|
||||
write!(formatter, " (behind by {local_ahead_count} commits)")?;
|
||||
} else if remote_ahead_count != 0 && local_ahead_count != 0 {
|
||||
write!(
|
||||
formatter,
|
||||
" (ahead by {remote_ahead_count} commits, behind by {local_ahead_count} \
|
||||
commits)"
|
||||
)?;
|
||||
}
|
||||
}
|
||||
print_branch_target(formatter, Some(remote_target))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_fast_forward(repo: RepoRef, branch_name: &str, new_target_id: &CommitId) -> bool {
|
||||
if let Some(current_target) = repo.view().get_local_branch(branch_name) {
|
||||
current_target
|
||||
.adds()
|
||||
.iter()
|
||||
.any(|add| repo.index().is_ancestor(add, new_target_id))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
|
@ -12,9 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod branch;
|
||||
mod git;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::path::PathBuf;
|
||||
|
@ -30,16 +31,15 @@ use jujutsu_lib::commit::Commit;
|
|||
use jujutsu_lib::dag_walk::topo_order_reverse;
|
||||
use jujutsu_lib::index::IndexEntry;
|
||||
use jujutsu_lib::matchers::EverythingMatcher;
|
||||
use jujutsu_lib::op_store::{RefTarget, WorkspaceId};
|
||||
use jujutsu_lib::op_store::WorkspaceId;
|
||||
use jujutsu_lib::operation::Operation;
|
||||
use jujutsu_lib::repo::{ReadonlyRepo, RepoRef};
|
||||
use jujutsu_lib::repo::ReadonlyRepo;
|
||||
use jujutsu_lib::repo_path::RepoPath;
|
||||
use jujutsu_lib::revset::{RevsetAliasesMap, RevsetExpression};
|
||||
use jujutsu_lib::revset_graph_iterator::{RevsetGraphEdge, RevsetGraphEdgeType};
|
||||
use jujutsu_lib::rewrite::{back_out_commit, merge_commit_trees, rebase_commit, DescendantRebaser};
|
||||
use jujutsu_lib::settings::UserSettings;
|
||||
use jujutsu_lib::tree::{merge_trees, Tree};
|
||||
use jujutsu_lib::view::View;
|
||||
use jujutsu_lib::workspace::{Workspace, WorkspaceLoader};
|
||||
use jujutsu_lib::{conflicts, file_util, revset};
|
||||
use maplit::{hashmap, hashset};
|
||||
|
@ -104,7 +104,7 @@ enum Commands {
|
|||
Rebase(RebaseArgs),
|
||||
Backout(BackoutArgs),
|
||||
#[command(subcommand)]
|
||||
Branch(BranchSubcommand),
|
||||
Branch(branch::BranchSubcommand),
|
||||
/// Undo an operation (shortcut for `jj op undo`)
|
||||
Undo(OperationUndoArgs),
|
||||
#[command(subcommand)]
|
||||
|
@ -741,76 +741,6 @@ struct BackoutArgs {
|
|||
destination: Vec<RevisionArg>,
|
||||
}
|
||||
|
||||
/// Manage branches.
|
||||
///
|
||||
/// For information about branches, see
|
||||
/// https://github.com/martinvonz/jj/blob/main/docs/branches.md.
|
||||
#[derive(clap::Subcommand, Clone, Debug)]
|
||||
enum BranchSubcommand {
|
||||
/// Create a new branch.
|
||||
#[command(visible_alias("c"))]
|
||||
Create {
|
||||
/// The branch's target revision.
|
||||
#[arg(long, short)]
|
||||
revision: Option<RevisionArg>,
|
||||
|
||||
/// The branches to create.
|
||||
#[arg(required = true, value_parser=NonEmptyStringValueParser::new())]
|
||||
names: Vec<String>,
|
||||
},
|
||||
|
||||
/// Delete an existing branch and propagate the deletion to remotes on the
|
||||
/// next push.
|
||||
#[command(visible_alias("d"))]
|
||||
Delete {
|
||||
/// The branches to delete.
|
||||
#[arg(required = true)]
|
||||
names: Vec<String>,
|
||||
},
|
||||
|
||||
/// Forget everything about a branch, including its local and remote
|
||||
/// targets.
|
||||
///
|
||||
/// A forgotten branch will not impact remotes on future pushes. It will be
|
||||
/// recreated on future pulls if it still exists in the remote.
|
||||
#[command(visible_alias("f"))]
|
||||
Forget {
|
||||
/// The branches to forget.
|
||||
#[arg(required_unless_present_any(&["glob"]))]
|
||||
names: Vec<String>,
|
||||
|
||||
/// A glob pattern indicating branches to forget.
|
||||
#[arg(long)]
|
||||
glob: Vec<String>,
|
||||
},
|
||||
|
||||
/// List branches and their targets
|
||||
///
|
||||
/// A remote branch will be included only if its target is different from
|
||||
/// the local target. For a conflicted branch (both local and remote), old
|
||||
/// target revisions are preceded by a "-" and new target revisions are
|
||||
/// preceded by a "+". For information about branches, see
|
||||
/// https://github.com/martinvonz/jj/blob/main/docs/branches.md.
|
||||
#[command(visible_alias("l"))]
|
||||
List,
|
||||
|
||||
/// Update a given branch to point to a certain commit.
|
||||
#[command(visible_alias("s"))]
|
||||
Set {
|
||||
/// The branch's target revision.
|
||||
#[arg(long, short)]
|
||||
revision: Option<RevisionArg>,
|
||||
|
||||
/// Allow moving the branch backwards or sideways.
|
||||
#[arg(long, short = 'B')]
|
||||
allow_backwards: bool,
|
||||
|
||||
/// The branches to update.
|
||||
#[arg(required = true)]
|
||||
names: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Commands for working with the operation log
|
||||
///
|
||||
/// Commands for working with the operation log. For information about the
|
||||
|
@ -3003,17 +2933,6 @@ fn cmd_backout(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn is_fast_forward(repo: RepoRef, branch_name: &str, new_target_id: &CommitId) -> bool {
|
||||
if let Some(current_target) = repo.view().get_local_branch(branch_name) {
|
||||
current_target
|
||||
.adds()
|
||||
.iter()
|
||||
.any(|add| repo.index().is_ancestor(add, new_target_id))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn make_branch_term(branch_names: &[impl AsRef<str>]) -> String {
|
||||
match branch_names {
|
||||
[branch_name] => format!("branch {}", branch_name.as_ref()),
|
||||
|
@ -3026,233 +2945,6 @@ fn make_branch_term(branch_names: &[impl AsRef<str>]) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn cmd_branch(
|
||||
ui: &mut Ui,
|
||||
command: &CommandHelper,
|
||||
subcommand: &BranchSubcommand,
|
||||
) -> Result<(), CommandError> {
|
||||
let mut workspace_command = command.workspace_helper(ui)?;
|
||||
let view = workspace_command.repo().view();
|
||||
fn validate_branch_names_exist<'a>(
|
||||
view: &'a View,
|
||||
names: &'a [String],
|
||||
) -> Result<(), CommandError> {
|
||||
for branch_name in names {
|
||||
if view.get_local_branch(branch_name).is_none() {
|
||||
return Err(user_error(format!("No such branch: {branch_name}")));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_globs(view: &View, globs: &[String]) -> Result<Vec<String>, CommandError> {
|
||||
let globs: Vec<glob::Pattern> = globs
|
||||
.iter()
|
||||
.map(|glob| glob::Pattern::new(glob))
|
||||
.try_collect()?;
|
||||
let matching_branches = view
|
||||
.branches()
|
||||
.iter()
|
||||
.map(|(branch_name, _branch_target)| branch_name)
|
||||
.filter(|branch_name| globs.iter().any(|glob| glob.matches(branch_name)))
|
||||
.cloned()
|
||||
.collect();
|
||||
Ok(matching_branches)
|
||||
}
|
||||
|
||||
match subcommand {
|
||||
BranchSubcommand::Create { revision, names } => {
|
||||
let branch_names: Vec<&str> = names
|
||||
.iter()
|
||||
.map(|branch_name| match view.get_local_branch(branch_name) {
|
||||
Some(_) => Err(user_error_with_hint(
|
||||
format!("Branch already exists: {branch_name}"),
|
||||
"Use `jj branch set` to update it.",
|
||||
)),
|
||||
None => Ok(branch_name.as_str()),
|
||||
})
|
||||
.try_collect()?;
|
||||
|
||||
if branch_names.len() > 1 {
|
||||
writeln!(
|
||||
ui.warning(),
|
||||
"warning: Creating multiple branches ({}).",
|
||||
branch_names.len()
|
||||
)?;
|
||||
}
|
||||
|
||||
let target_commit =
|
||||
workspace_command.resolve_single_rev(revision.as_deref().unwrap_or("@"))?;
|
||||
let mut tx = workspace_command.start_transaction(&format!(
|
||||
"create {} pointing to commit {}",
|
||||
make_branch_term(&branch_names),
|
||||
target_commit.id().hex()
|
||||
));
|
||||
for branch_name in branch_names {
|
||||
tx.mut_repo().set_local_branch(
|
||||
branch_name.to_string(),
|
||||
RefTarget::Normal(target_commit.id().clone()),
|
||||
);
|
||||
}
|
||||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
||||
BranchSubcommand::Set {
|
||||
revision,
|
||||
allow_backwards,
|
||||
names: branch_names,
|
||||
} => {
|
||||
if branch_names.len() > 1 {
|
||||
writeln!(
|
||||
ui.warning(),
|
||||
"warning: Updating multiple branches ({}).",
|
||||
branch_names.len()
|
||||
)?;
|
||||
}
|
||||
|
||||
let target_commit =
|
||||
workspace_command.resolve_single_rev(revision.as_deref().unwrap_or("@"))?;
|
||||
if !allow_backwards
|
||||
&& !branch_names.iter().all(|branch_name| {
|
||||
is_fast_forward(
|
||||
workspace_command.repo().as_repo_ref(),
|
||||
branch_name,
|
||||
target_commit.id(),
|
||||
)
|
||||
})
|
||||
{
|
||||
return Err(user_error_with_hint(
|
||||
"Refusing to move branch backwards or sideways.",
|
||||
"Use --allow-backwards to allow it.",
|
||||
));
|
||||
}
|
||||
let mut tx = workspace_command.start_transaction(&format!(
|
||||
"point {} to commit {}",
|
||||
make_branch_term(branch_names),
|
||||
target_commit.id().hex()
|
||||
));
|
||||
for branch_name in branch_names {
|
||||
tx.mut_repo().set_local_branch(
|
||||
branch_name.to_string(),
|
||||
RefTarget::Normal(target_commit.id().clone()),
|
||||
);
|
||||
}
|
||||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
||||
BranchSubcommand::Delete { names } => {
|
||||
validate_branch_names_exist(view, names)?;
|
||||
let mut tx =
|
||||
workspace_command.start_transaction(&format!("delete {}", make_branch_term(names)));
|
||||
for branch_name in names {
|
||||
tx.mut_repo().remove_local_branch(branch_name);
|
||||
}
|
||||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
||||
BranchSubcommand::Forget { names, glob } => {
|
||||
validate_branch_names_exist(view, names)?;
|
||||
let globbed_names = find_globs(view, glob)?;
|
||||
let names: BTreeSet<String> = names.iter().cloned().chain(globbed_names).collect();
|
||||
let branch_term = make_branch_term(names.iter().collect_vec().as_slice());
|
||||
let mut tx = workspace_command.start_transaction(&format!("forget {branch_term}"));
|
||||
for branch_name in names {
|
||||
tx.mut_repo().remove_branch(&branch_name);
|
||||
}
|
||||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
||||
BranchSubcommand::List => {
|
||||
list_branches(ui, command, &workspace_command)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_branches(
|
||||
ui: &mut Ui,
|
||||
_command: &CommandHelper,
|
||||
workspace_command: &WorkspaceCommandHelper,
|
||||
) -> Result<(), CommandError> {
|
||||
let repo = workspace_command.repo();
|
||||
|
||||
let print_branch_target =
|
||||
|formatter: &mut dyn Formatter, target: Option<&RefTarget>| -> Result<(), CommandError> {
|
||||
match target {
|
||||
Some(RefTarget::Normal(id)) => {
|
||||
write!(formatter, ": ")?;
|
||||
let commit = repo.store().get_commit(id)?;
|
||||
workspace_command.write_commit_summary(formatter, &commit)?;
|
||||
writeln!(formatter)?;
|
||||
}
|
||||
Some(RefTarget::Conflict { adds, removes }) => {
|
||||
write!(formatter, " ")?;
|
||||
write!(formatter.labeled("conflict"), "(conflicted)")?;
|
||||
writeln!(formatter, ":")?;
|
||||
for id in removes {
|
||||
let commit = repo.store().get_commit(id)?;
|
||||
write!(formatter, " - ")?;
|
||||
workspace_command.write_commit_summary(formatter, &commit)?;
|
||||
writeln!(formatter)?;
|
||||
}
|
||||
for id in adds {
|
||||
let commit = repo.store().get_commit(id)?;
|
||||
write!(formatter, " + ")?;
|
||||
workspace_command.write_commit_summary(formatter, &commit)?;
|
||||
writeln!(formatter)?;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
writeln!(formatter, " (deleted)")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
let formatter = formatter.as_mut();
|
||||
let index = repo.index();
|
||||
for (name, branch_target) in repo.view().branches() {
|
||||
write!(formatter.labeled("branch"), "{name}")?;
|
||||
print_branch_target(formatter, branch_target.local_target.as_ref())?;
|
||||
|
||||
for (remote, remote_target) in branch_target
|
||||
.remote_targets
|
||||
.iter()
|
||||
.sorted_by_key(|(name, _target)| name.to_owned())
|
||||
{
|
||||
if Some(remote_target) == branch_target.local_target.as_ref() {
|
||||
continue;
|
||||
}
|
||||
write!(formatter, " ")?;
|
||||
write!(formatter.labeled("branch"), "@{remote}")?;
|
||||
if let Some(local_target) = branch_target.local_target.as_ref() {
|
||||
let remote_ahead_count = index
|
||||
.walk_revs(&remote_target.adds(), &local_target.adds())
|
||||
.count();
|
||||
let local_ahead_count = index
|
||||
.walk_revs(&local_target.adds(), &remote_target.adds())
|
||||
.count();
|
||||
if remote_ahead_count != 0 && local_ahead_count == 0 {
|
||||
write!(formatter, " (ahead by {remote_ahead_count} commits)")?;
|
||||
} else if remote_ahead_count == 0 && local_ahead_count != 0 {
|
||||
write!(formatter, " (behind by {local_ahead_count} commits)")?;
|
||||
} else if remote_ahead_count != 0 && local_ahead_count != 0 {
|
||||
write!(
|
||||
formatter,
|
||||
" (ahead by {remote_ahead_count} commits, behind by {local_ahead_count} \
|
||||
commits)"
|
||||
)?;
|
||||
}
|
||||
}
|
||||
print_branch_target(formatter, Some(remote_target))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd_debug(
|
||||
ui: &mut Ui,
|
||||
command: &CommandHelper,
|
||||
|
@ -3763,7 +3455,7 @@ pub fn run_command(
|
|||
Commands::Rebase(sub_args) => cmd_rebase(ui, command_helper, sub_args),
|
||||
Commands::Backout(sub_args) => cmd_backout(ui, command_helper, sub_args),
|
||||
Commands::Resolve(sub_args) => cmd_resolve(ui, command_helper, sub_args),
|
||||
Commands::Branch(sub_args) => cmd_branch(ui, command_helper, sub_args),
|
||||
Commands::Branch(sub_args) => branch::cmd_branch(ui, command_helper, sub_args),
|
||||
Commands::Undo(sub_args) => cmd_op_undo(ui, command_helper, sub_args),
|
||||
Commands::Operation(sub_args) => cmd_operation(ui, command_helper, sub_args),
|
||||
Commands::Workspace(sub_args) => cmd_workspace(ui, command_helper, sub_args),
|
||||
|
|
Loading…
Reference in a new issue