// Copyright 2020-2023 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::collections::HashSet;use std::fmt;use std::io::Write as _; use clap::builder::NonEmptyStringValueParser;use itertools::Itertools;use jj_lib::backend::CommitId;use jj_lib::git;use jj_lib::object_id::ObjectId;use jj_lib::op_store::{RefTarget, RemoteRef};use jj_lib::repo::Repo;use jj_lib::revset::{self, RevsetExpression};use jj_lib::str_util::StringPattern;use jj_lib::view::View; use crate::cli_util::{ parse_string_pattern, CommandHelper, RemoteBranchName, RemoteBranchNamePattern, RevisionArg,};use crate::command_error::{user_error, user_error_with_hint, CommandError};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 BranchCommand { #[command(visible_alias("c"))] Create(BranchCreateArgs), #[command(visible_alias("d"))] Delete(BranchDeleteArgs), #[command(visible_alias("f"))] Forget(BranchForgetArgs), #[command(visible_alias("l"))] List(BranchListArgs), #[command(visible_alias("r"))] Rename(BranchRenameArgs), #[command(visible_alias("s"))] Set(BranchSetArgs), #[command(visible_alias("t"))] Track(BranchTrackArgs), Untrack(BranchUntrackArgs),} /// Create a new branch.#[derive(clap::Args, Clone, Debug)]pub struct BranchCreateArgs { /// 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.#[derive(clap::Args, Clone, Debug)]pub struct BranchDeleteArgs { /// The branches to delete /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(required_unless_present_any(&["glob"]), value_parser = parse_string_pattern)] pub names: Vec<StringPattern>, /// Deprecated. Please prefix the pattern with `glob:` instead. #[arg(long, hide = true, value_parser = StringPattern::glob)] pub glob: Vec<StringPattern>,} /// List branches and their targets////// By default, a tracking remote branch will be included only if its target is/// different from the local target. A non-tracking remote branch won't be/// listed. 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.#[derive(clap::Args, Clone, Debug)]pub struct BranchListArgs { /// Show all tracking and non-tracking remote branches including the ones /// whose targets are synchronized with the local branches. #[arg(long, short, conflicts_with_all = ["names", "revisions", "tracked"])] all: bool, /// Show remote tracked branches only. Omits local Git-tracking branches by /// default. #[arg(long, short, conflicts_with_all = ["all"])] tracked: bool, /// Show branches whose local name matches /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(value_parser = parse_string_pattern)] pub names: Vec<StringPattern>, /// Show branches whose local targets are in the given revisions. /// /// Note that `-r deleted_branch` will not work since `deleted_branch` /// wouldn't have a local target. #[arg(long, short)] revisions: Vec<RevisionArg>,} /// 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.#[derive(clap::Args, Clone, Debug)]pub struct BranchForgetArgs { /// The branches to forget /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(required_unless_present_any(&["glob"]), value_parser = parse_string_pattern)] pub names: Vec<StringPattern>, /// Deprecated. Please prefix the pattern with `glob:` instead. #[arg(long, hide = true, value_parser = StringPattern::glob)] pub glob: Vec<StringPattern>,} /// Rename `old` branch name to `new` branch name.////// The new branch name points at the same commit as the old/// branch name.#[derive(clap::Args, Clone, Debug)]pub struct BranchRenameArgs { /// The old name of the branch. pub old: String, /// The new name of the branch. pub new: String,} /// Update an existing branch to point to a certain commit.#[derive(clap::Args, Clone, Debug)]pub struct BranchSetArgs { /// The branch's target revision. #[arg(long, short)] pub revision: Option<RevisionArg>, /// Allow moving the branch backwards or sideways. #[arg(long, short = 'B')] pub allow_backwards: bool, /// The branches to update. #[arg(required = true)] pub names: Vec<String>,} /// Start tracking given remote branches////// A tracking remote branch will be imported as a local branch of the same/// name. Changes to it will propagate to the existing local branch on future/// pulls.#[derive(clap::Args, Clone, Debug)]pub struct BranchTrackArgs { /// Remote branches to track /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. /// /// Examples: branch@remote, glob:main@*, glob:jjfan-*@upstream #[arg(required = true, value_name = "BRANCH@REMOTE")] pub names: Vec<RemoteBranchNamePattern>,} /// Stop tracking given remote branches////// A non-tracking remote branch is just a pointer to the last-fetched remote/// branch. It won't be imported as a local branch on future pulls.#[derive(clap::Args, Clone, Debug)]pub struct BranchUntrackArgs { /// Remote branches to untrack /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. /// /// Examples: branch@remote, glob:main@*, glob:jjfan-*@upstream #[arg(required = true, value_name = "BRANCH@REMOTE")] pub names: Vec<RemoteBranchNamePattern>,} fn make_branch_term(branch_names: &[impl fmt::Display]) -> String { match branch_names { [branch_name] => format!("branch {}", branch_name), branch_names => format!("branches {}", branch_names.iter().join(", ")), }} pub fn cmd_branch( ui: &mut Ui, command: &CommandHelper, subcommand: &BranchCommand,) -> Result<(), CommandError> { match subcommand { BranchCommand::Create(sub_args) => cmd_branch_create(ui, command, sub_args), BranchCommand::Rename(sub_args) => cmd_branch_rename(ui, command, sub_args), BranchCommand::Set(sub_args) => cmd_branch_set(ui, command, sub_args), BranchCommand::Delete(sub_args) => cmd_branch_delete(ui, command, sub_args), BranchCommand::Forget(sub_args) => cmd_branch_forget(ui, command, sub_args), BranchCommand::Track(sub_args) => cmd_branch_track(ui, command, sub_args), BranchCommand::Untrack(sub_args) => cmd_branch_untrack(ui, command, sub_args), BranchCommand::List(sub_args) => cmd_branch_list(ui, command, sub_args), }} fn cmd_branch_create( ui: &mut Ui, command: &CommandHelper, args: &BranchCreateArgs,) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let target_commit = workspace_command.resolve_single_rev(args.revision.as_deref().unwrap_or("@"))?; let view = workspace_command.repo().view(); let branch_names = &args.names; if let Some(branch_name) = branch_names .iter() .find(|&name| view.get_local_branch(name).is_present()) { return Err(user_error_with_hint( format!("Branch already exists: {branch_name}"), "Use `jj branch set` to update it.", )); } if branch_names.len() > 1 { writeln!( ui.warning(), "warning: Creating multiple branches: {}", branch_names.join(", "), )?; } let mut tx = workspace_command.start_transaction(); for branch_name in branch_names { tx.mut_repo() .set_local_branch_target(branch_name, RefTarget::normal(target_commit.id().clone())); } tx.finish( ui, format!( "create {} pointing to commit {}", make_branch_term(branch_names), target_commit.id().hex() ), )?; Ok(())} fn cmd_branch_rename( ui: &mut Ui, command: &CommandHelper, args: &BranchRenameArgs,) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let view = workspace_command.repo().view(); let old_branch = &args.old; let ref_target = view.get_local_branch(old_branch).clone(); if ref_target.is_absent() { return Err(user_error(format!("No such branch: {old_branch}"))); } let new_branch = &args.new; if view.get_local_branch(new_branch).is_present() { return Err(user_error(format!("Branch already exists: {new_branch}"))); } let mut tx = workspace_command.start_transaction(); tx.mut_repo() .set_local_branch_target(new_branch, ref_target); tx.mut_repo() .set_local_branch_target(old_branch, RefTarget::absent()); tx.finish( ui, format!( "rename {} to {}", make_branch_term(&[old_branch]), make_branch_term(&[new_branch]), ), )?; let view = workspace_command.repo().view(); if view .remote_branches_matching( &StringPattern::exact(old_branch), &StringPattern::everything(), ) .any(|(_, remote_ref)| remote_ref.is_tracking()) { writeln!( ui.warning(), "Warning: Branch {old_branch} has tracking remote branches which were not renamed." )?; writeln!( ui.hint(), "Hint: to rename the branch on the remote, you can `jj git push --branch \ {old_branch}` first (to delete it on the remote), and then `jj git push --branch \ {new_branch}`. `jj git push --all` would also be sufficient." )?; } Ok(())} fn cmd_branch_set( ui: &mut Ui, command: &CommandHelper, args: &BranchSetArgs,) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let target_commit = workspace_command.resolve_single_rev(args.revision.as_deref().unwrap_or("@"))?; let repo = workspace_command.repo().as_ref(); let is_fast_forward = |old_target: &RefTarget| { // Strictly speaking, "all" old targets should be ancestors, but we allow // conflict resolution by setting branch to "any" of the old target descendants. old_target .added_ids() .any(|old| repo.index().is_ancestor(old, target_commit.id())) }; let branch_names = &args.names; for name in branch_names { let old_target = repo.view().get_local_branch(name); if old_target.is_absent() { return Err(user_error_with_hint( format!("No such branch: {name}"), "Use `jj branch create` to create it.", )); } if !args.allow_backwards && !is_fast_forward(old_target) { return Err(user_error_with_hint( format!("Refusing to move branch backwards or sideways: {name}"), "Use --allow-backwards to allow it.", )); } } if branch_names.len() > 1 { writeln!( ui.warning(), "warning: Updating multiple branches: {}", branch_names.join(", "), )?; } let mut tx = workspace_command.start_transaction(); for branch_name in branch_names { tx.mut_repo() .set_local_branch_target(branch_name, RefTarget::normal(target_commit.id().clone())); } tx.finish( ui, format!( "point {} to commit {}", make_branch_term(branch_names), target_commit.id().hex() ), )?; Ok(())} fn find_local_branches( view: &View, name_patterns: &[StringPattern],) -> Result<Vec<String>, CommandError> { find_branches_with(name_patterns, |pattern| { view.local_branches_matching(pattern) .map(|(name, _)| name.to_owned()) })} fn find_forgettable_branches( view: &View, name_patterns: &[StringPattern],) -> Result<Vec<String>, CommandError> { find_branches_with(name_patterns, |pattern| { view.branches() .filter(|(name, _)| pattern.matches(name)) .map(|(name, _)| name.to_owned()) })} fn find_branches_with<'a, I: Iterator<Item = String>>( name_patterns: &'a [StringPattern], mut find_matches: impl FnMut(&'a StringPattern) -> I,) -> Result<Vec<String>, CommandError> { let mut matching_branches: Vec<String> = vec![]; let mut unmatched_patterns = vec![]; for pattern in name_patterns { let mut names = find_matches(pattern).peekable(); if names.peek().is_none() { unmatched_patterns.push(pattern); } matching_branches.extend(names); } match &unmatched_patterns[..] { [] => { matching_branches.sort_unstable(); matching_branches.dedup(); Ok(matching_branches) } [pattern] if pattern.is_exact() => Err(user_error(format!("No such branch: {pattern}"))), patterns => Err(user_error(format!( "No matching branches for patterns: {}", patterns.iter().join(", ") ))), }} fn find_remote_branches<'a>( view: &'a View, name_patterns: &[RemoteBranchNamePattern],) -> Result<Vec<(RemoteBranchName, &'a RemoteRef)>, CommandError> { let mut matching_branches = vec![]; let mut unmatched_patterns = vec![]; for pattern in name_patterns { let mut matches = view .remote_branches_matching(&pattern.branch, &pattern.remote) .map(|((branch, remote), remote_ref)| { let name = RemoteBranchName { branch: branch.to_owned(), remote: remote.to_owned(), }; (name, remote_ref) }) .peekable(); if matches.peek().is_none() { unmatched_patterns.push(pattern); } matching_branches.extend(matches); } match &unmatched_patterns[..] { [] => { matching_branches.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); matching_branches.dedup_by(|(name1, _), (name2, _)| name1 == name2); Ok(matching_branches) } [pattern] if pattern.is_exact() => { Err(user_error(format!("No such remote branch: {pattern}"))) } patterns => Err(user_error(format!( "No matching remote branches for patterns: {}", patterns.iter().join(", ") ))), }} fn cmd_branch_delete( ui: &mut Ui, command: &CommandHelper, args: &BranchDeleteArgs,) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let view = workspace_command.repo().view(); if !args.glob.is_empty() { writeln!( ui.warning(), "--glob has been deprecated. Please prefix the pattern with `glob:` instead." )?; } let name_patterns = [&args.names[..], &args.glob[..]].concat(); let names = find_local_branches(view, &name_patterns)?; let mut tx = workspace_command.start_transaction(); for branch_name in names.iter() { tx.mut_repo() .set_local_branch_target(branch_name, RefTarget::absent()); } tx.finish(ui, format!("delete {}", make_branch_term(&names)))?; if names.len() > 1 { writeln!(ui.stderr(), "Deleted {} branches.", names.len())?; }Lines 1–500 of 781. Load more
// Copyright 2020-2023 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::collections::HashSet; use std::fmt; use std::io::Write as _; use clap::builder::NonEmptyStringValueParser; use itertools::Itertools; use jj_lib::backend::CommitId; use jj_lib::git; use jj_lib::object_id::ObjectId; use jj_lib::op_store::{RefTarget, RemoteRef}; use jj_lib::repo::Repo; use jj_lib::revset::{self, RevsetExpression}; use jj_lib::str_util::StringPattern; use jj_lib::view::View; use crate::cli_util::{ parse_string_pattern, CommandHelper, RemoteBranchName, RemoteBranchNamePattern, RevisionArg, }; use crate::command_error::{user_error, user_error_with_hint, CommandError}; 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 BranchCommand { #[command(visible_alias("c"))] Create(BranchCreateArgs), #[command(visible_alias("d"))] Delete(BranchDeleteArgs), #[command(visible_alias("f"))] Forget(BranchForgetArgs), #[command(visible_alias("l"))] List(BranchListArgs), #[command(visible_alias("r"))] Rename(BranchRenameArgs), #[command(visible_alias("s"))] Set(BranchSetArgs), #[command(visible_alias("t"))] Track(BranchTrackArgs), Untrack(BranchUntrackArgs), } /// Create a new branch. #[derive(clap::Args, Clone, Debug)] pub struct BranchCreateArgs { /// 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. #[derive(clap::Args, Clone, Debug)] pub struct BranchDeleteArgs { /// The branches to delete /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(required_unless_present_any(&["glob"]), value_parser = parse_string_pattern)] pub names: Vec<StringPattern>, /// Deprecated. Please prefix the pattern with `glob:` instead. #[arg(long, hide = true, value_parser = StringPattern::glob)] pub glob: Vec<StringPattern>, } /// List branches and their targets /// /// By default, a tracking remote branch will be included only if its target is /// different from the local target. A non-tracking remote branch won't be /// listed. 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. #[derive(clap::Args, Clone, Debug)] pub struct BranchListArgs { /// Show all tracking and non-tracking remote branches including the ones /// whose targets are synchronized with the local branches. #[arg(long, short, conflicts_with_all = ["names", "revisions", "tracked"])] all: bool, /// Show remote tracked branches only. Omits local Git-tracking branches by /// default. #[arg(long, short, conflicts_with_all = ["all"])] tracked: bool, /// Show branches whose local name matches /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(value_parser = parse_string_pattern)] pub names: Vec<StringPattern>, /// Show branches whose local targets are in the given revisions. /// /// Note that `-r deleted_branch` will not work since `deleted_branch` /// wouldn't have a local target. #[arg(long, short)] revisions: Vec<RevisionArg>, } /// 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. #[derive(clap::Args, Clone, Debug)] pub struct BranchForgetArgs { /// The branches to forget /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(required_unless_present_any(&["glob"]), value_parser = parse_string_pattern)] pub names: Vec<StringPattern>, /// Deprecated. Please prefix the pattern with `glob:` instead. #[arg(long, hide = true, value_parser = StringPattern::glob)] pub glob: Vec<StringPattern>, } /// Rename `old` branch name to `new` branch name. /// /// The new branch name points at the same commit as the old /// branch name. #[derive(clap::Args, Clone, Debug)] pub struct BranchRenameArgs { /// The old name of the branch. pub old: String, /// The new name of the branch. pub new: String, } /// Update an existing branch to point to a certain commit. #[derive(clap::Args, Clone, Debug)] pub struct BranchSetArgs { /// The branch's target revision. #[arg(long, short)] pub revision: Option<RevisionArg>, /// Allow moving the branch backwards or sideways. #[arg(long, short = 'B')] pub allow_backwards: bool, /// The branches to update. #[arg(required = true)] pub names: Vec<String>, } /// Start tracking given remote branches /// /// A tracking remote branch will be imported as a local branch of the same /// name. Changes to it will propagate to the existing local branch on future /// pulls. #[derive(clap::Args, Clone, Debug)] pub struct BranchTrackArgs { /// Remote branches to track /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. /// /// Examples: branch@remote, glob:main@*, glob:jjfan-*@upstream #[arg(required = true, value_name = "BRANCH@REMOTE")] pub names: Vec<RemoteBranchNamePattern>, } /// Stop tracking given remote branches /// /// A non-tracking remote branch is just a pointer to the last-fetched remote /// branch. It won't be imported as a local branch on future pulls. #[derive(clap::Args, Clone, Debug)] pub struct BranchUntrackArgs { /// Remote branches to untrack /// /// By default, the specified name matches exactly. Use `glob:` prefix to /// select branches by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. /// /// Examples: branch@remote, glob:main@*, glob:jjfan-*@upstream #[arg(required = true, value_name = "BRANCH@REMOTE")] pub names: Vec<RemoteBranchNamePattern>, } fn make_branch_term(branch_names: &[impl fmt::Display]) -> String { match branch_names { [branch_name] => format!("branch {}", branch_name), branch_names => format!("branches {}", branch_names.iter().join(", ")), } } pub fn cmd_branch( ui: &mut Ui, command: &CommandHelper, subcommand: &BranchCommand, ) -> Result<(), CommandError> { match subcommand { BranchCommand::Create(sub_args) => cmd_branch_create(ui, command, sub_args), BranchCommand::Rename(sub_args) => cmd_branch_rename(ui, command, sub_args), BranchCommand::Set(sub_args) => cmd_branch_set(ui, command, sub_args), BranchCommand::Delete(sub_args) => cmd_branch_delete(ui, command, sub_args), BranchCommand::Forget(sub_args) => cmd_branch_forget(ui, command, sub_args), BranchCommand::Track(sub_args) => cmd_branch_track(ui, command, sub_args), BranchCommand::Untrack(sub_args) => cmd_branch_untrack(ui, command, sub_args), BranchCommand::List(sub_args) => cmd_branch_list(ui, command, sub_args), } } fn cmd_branch_create( ui: &mut Ui, command: &CommandHelper, args: &BranchCreateArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let target_commit = workspace_command.resolve_single_rev(args.revision.as_deref().unwrap_or("@"))?; let view = workspace_command.repo().view(); let branch_names = &args.names; if let Some(branch_name) = branch_names .iter() .find(|&name| view.get_local_branch(name).is_present()) { return Err(user_error_with_hint( format!("Branch already exists: {branch_name}"), "Use `jj branch set` to update it.", )); } if branch_names.len() > 1 { writeln!( ui.warning(), "warning: Creating multiple branches: {}", branch_names.join(", "), )?; } let mut tx = workspace_command.start_transaction(); for branch_name in branch_names { tx.mut_repo() .set_local_branch_target(branch_name, RefTarget::normal(target_commit.id().clone())); } tx.finish( ui, format!( "create {} pointing to commit {}", make_branch_term(branch_names), target_commit.id().hex() ), )?; Ok(()) } fn cmd_branch_rename( ui: &mut Ui, command: &CommandHelper, args: &BranchRenameArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let view = workspace_command.repo().view(); let old_branch = &args.old; let ref_target = view.get_local_branch(old_branch).clone(); if ref_target.is_absent() { return Err(user_error(format!("No such branch: {old_branch}"))); } let new_branch = &args.new; if view.get_local_branch(new_branch).is_present() { return Err(user_error(format!("Branch already exists: {new_branch}"))); } let mut tx = workspace_command.start_transaction(); tx.mut_repo() .set_local_branch_target(new_branch, ref_target); tx.mut_repo() .set_local_branch_target(old_branch, RefTarget::absent()); tx.finish( ui, format!( "rename {} to {}", make_branch_term(&[old_branch]), make_branch_term(&[new_branch]), ), )?; let view = workspace_command.repo().view(); if view .remote_branches_matching( &StringPattern::exact(old_branch), &StringPattern::everything(), ) .any(|(_, remote_ref)| remote_ref.is_tracking()) { writeln!( ui.warning(), "Warning: Branch {old_branch} has tracking remote branches which were not renamed." )?; writeln!( ui.hint(), "Hint: to rename the branch on the remote, you can `jj git push --branch \ {old_branch}` first (to delete it on the remote), and then `jj git push --branch \ {new_branch}`. `jj git push --all` would also be sufficient." )?; } Ok(()) } fn cmd_branch_set( ui: &mut Ui, command: &CommandHelper, args: &BranchSetArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let target_commit = workspace_command.resolve_single_rev(args.revision.as_deref().unwrap_or("@"))?; let repo = workspace_command.repo().as_ref(); let is_fast_forward = |old_target: &RefTarget| { // Strictly speaking, "all" old targets should be ancestors, but we allow // conflict resolution by setting branch to "any" of the old target descendants. old_target .added_ids() .any(|old| repo.index().is_ancestor(old, target_commit.id())) }; let branch_names = &args.names; for name in branch_names { let old_target = repo.view().get_local_branch(name); if old_target.is_absent() { return Err(user_error_with_hint( format!("No such branch: {name}"), "Use `jj branch create` to create it.", )); } if !args.allow_backwards && !is_fast_forward(old_target) { return Err(user_error_with_hint( format!("Refusing to move branch backwards or sideways: {name}"), "Use --allow-backwards to allow it.", )); } } if branch_names.len() > 1 { writeln!( ui.warning(), "warning: Updating multiple branches: {}", branch_names.join(", "), )?; } let mut tx = workspace_command.start_transaction(); for branch_name in branch_names { tx.mut_repo() .set_local_branch_target(branch_name, RefTarget::normal(target_commit.id().clone())); } tx.finish( ui, format!( "point {} to commit {}", make_branch_term(branch_names), target_commit.id().hex() ), )?; Ok(()) } fn find_local_branches( view: &View, name_patterns: &[StringPattern], ) -> Result<Vec<String>, CommandError> { find_branches_with(name_patterns, |pattern| { view.local_branches_matching(pattern) .map(|(name, _)| name.to_owned()) }) } fn find_forgettable_branches( view: &View, name_patterns: &[StringPattern], ) -> Result<Vec<String>, CommandError> { find_branches_with(name_patterns, |pattern| { view.branches() .filter(|(name, _)| pattern.matches(name)) .map(|(name, _)| name.to_owned()) }) } fn find_branches_with<'a, I: Iterator<Item = String>>( name_patterns: &'a [StringPattern], mut find_matches: impl FnMut(&'a StringPattern) -> I, ) -> Result<Vec<String>, CommandError> { let mut matching_branches: Vec<String> = vec![]; let mut unmatched_patterns = vec![]; for pattern in name_patterns { let mut names = find_matches(pattern).peekable(); if names.peek().is_none() { unmatched_patterns.push(pattern); } matching_branches.extend(names); } match &unmatched_patterns[..] { [] => { matching_branches.sort_unstable(); matching_branches.dedup(); Ok(matching_branches) } [pattern] if pattern.is_exact() => Err(user_error(format!("No such branch: {pattern}"))), patterns => Err(user_error(format!( "No matching branches for patterns: {}", patterns.iter().join(", ") ))), } } fn find_remote_branches<'a>( view: &'a View, name_patterns: &[RemoteBranchNamePattern], ) -> Result<Vec<(RemoteBranchName, &'a RemoteRef)>, CommandError> { let mut matching_branches = vec![]; let mut unmatched_patterns = vec![]; for pattern in name_patterns { let mut matches = view .remote_branches_matching(&pattern.branch, &pattern.remote) .map(|((branch, remote), remote_ref)| { let name = RemoteBranchName { branch: branch.to_owned(), remote: remote.to_owned(), }; (name, remote_ref) }) .peekable(); if matches.peek().is_none() { unmatched_patterns.push(pattern); } matching_branches.extend(matches); } match &unmatched_patterns[..] { [] => { matching_branches.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); matching_branches.dedup_by(|(name1, _), (name2, _)| name1 == name2); Ok(matching_branches) } [pattern] if pattern.is_exact() => { Err(user_error(format!("No such remote branch: {pattern}"))) } patterns => Err(user_error(format!( "No matching remote branches for patterns: {}", patterns.iter().join(", ") ))), } } fn cmd_branch_delete( ui: &mut Ui, command: &CommandHelper, args: &BranchDeleteArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let view = workspace_command.repo().view(); if !args.glob.is_empty() { writeln!( ui.warning(), "--glob has been deprecated. Please prefix the pattern with `glob:` instead." )?; } let name_patterns = [&args.names[..], &args.glob[..]].concat(); let names = find_local_branches(view, &name_patterns)?; let mut tx = workspace_command.start_transaction(); for branch_name in names.iter() { tx.mut_repo() .set_local_branch_target(branch_name, RefTarget::absent()); } tx.finish(ui, format!("delete {}", make_branch_term(&names)))?; if names.len() > 1 { writeln!(ui.stderr(), "Deleted {} branches.", names.len())?; }