diff --git a/CHANGELOG.md b/CHANGELOG.md index 859b617309..668a0b5c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,21 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). commit to commit. It now either follows the flags `--edit|--no-edit` or it gets the mode from `ui.movement.edit`. +* `jj branch` has been renamed to `jj bookmark`, this does not affect the + `[experimental-advance-branches]` option, to lessen the config churn. + ### Deprecations * `jj untrack` has been renamed to `jj file untrack`. +* `jj branch` has been deprecated in favor of `jj bookmark`. + + **Rationale:** Jujutsu Branches never behaved like Git branches, which a + confused a majority of newcomers, as they expected a similar behavior with + the given name. Since this was a major point of confusion we've renamed them + back to Bookmarks to match the actual behavior, as they're just a + bookmark on a `Change-Id` which gets rewritten if necessary. + ### New features * Add new boolean config knob, `ui.movement.edit` for controlling the behaviour diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index 0bb8f4c97c..c065635700 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -497,33 +497,33 @@ impl ReadonlyUserRepo { } } -/// A branch that should be advanced to satisfy the "advance-branches" feature. -/// This is a helper for `WorkspaceCommandTransaction`. It provides a type-safe -/// way to separate the work of checking whether a branch can be advanced and -/// actually advancing it. Advancing the branch never fails, but can't be done -/// until the new `CommitId` is available. Splitting the work in this way also -/// allows us to identify eligible branches without actually moving them and -/// return config errors to the user early. -pub struct AdvanceableBranch { +/// A bookmark that should be advanced to satisfy the "advance-bookmarks" +/// feature. This is a helper for `WorkspaceCommandTransaction`. It provides a +/// type-safe way to separate the work of checking whether a bookmark can be +/// advanced and actually advancing it. Advancing the bookmark never fails, but +/// can't be done until the new `CommitId` is available. Splitting the work in +/// this way also allows us to identify eligible bookmarks without actually +/// moving them and return config errors to the user early. +pub struct AdvanceableBookmark { name: String, old_commit_id: CommitId, } -/// Helper for parsing and evaluating settings for the advance-branches feature. -/// Settings are configured in the jj config.toml as lists of [`StringPattern`]s -/// for enabled and disabled branches. Example: +/// Helper for parsing and evaluating settings for the advance-bookmarks +/// feature. Settings are configured in the jj config.toml as lists of +/// [`StringPattern`]s for enabled and disabled bookmarks. Example: /// ```toml /// [experimental-advance-branches] /// # Enable the feature for all branches except "main". /// enabled-branches = ["glob:*"] /// disabled-branches = ["main"] /// ``` -struct AdvanceBranchesSettings { - enabled_branches: Vec, - disabled_branches: Vec, +struct AdvanceBookmarksSettings { + enabled_bookmarks: Vec, + disabled_bookmarks: Vec, } -impl AdvanceBranchesSettings { +impl AdvanceBookmarksSettings { fn from_config(config: &config::Config) -> Result { let get_setting = |setting_key| { let setting = format!("experimental-advance-branches.{setting_key}"); @@ -543,28 +543,30 @@ impl AdvanceBranchesSettings { } }; Ok(Self { - enabled_branches: get_setting("enabled-branches")?, - disabled_branches: get_setting("disabled-branches")?, + enabled_bookmarks: get_setting("enabled-branches")?, + disabled_bookmarks: get_setting("disabled-branches")?, }) } - /// Returns true if the advance-branches feature is enabled for - /// `branch_name`. - fn branch_is_eligible(&self, branch_name: &str) -> bool { + /// Returns true if the advance-bookmarks feature is enabled for + /// `bookmark_name`. + fn bookmark_is_eligible(&self, bookmark_name: &str) -> bool { if self - .disabled_branches + .disabled_bookmarks .iter() - .any(|d| d.matches(branch_name)) + .any(|d| d.matches(bookmark_name)) { return false; } - self.enabled_branches.iter().any(|e| e.matches(branch_name)) + self.enabled_bookmarks + .iter() + .any(|e| e.matches(bookmark_name)) } - /// Returns true if the config includes at least one "enabled-branches" + /// Returns true if the config includes at least one "enabled-bookmarks" /// pattern. fn feature_enabled(&self) -> bool { - !self.enabled_branches.is_empty() + !self.enabled_bookmarks.is_empty() } } @@ -725,11 +727,11 @@ impl WorkspaceCommandHelper { Ok(()) } - /// Imports branches and tags from the underlying Git repo, abandons old - /// branches. + /// Imports bookmarks and tags from the underlying Git repo, abandons old + /// bookmarks. /// - /// If the working-copy branch is rebased, and if update is allowed, the new - /// working-copy commit will be checked out. + /// If the working-copy bookmark is rebased, and if update is allowed, the + /// new working-copy commit will be checked out. /// /// This function does not import the Git HEAD, but the HEAD may be reset to /// the working copy parent if the repository is colocated. @@ -1365,8 +1367,8 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin } if self.working_copy_shared_with_git { - let failed_branches = git::export_refs(mut_repo)?; - print_failed_git_export(ui, &failed_branches)?; + let failed_bookmarks = git::export_refs(mut_repo)?; + print_failed_git_export(ui, &failed_bookmarks)?; } self.user_repo = ReadonlyUserRepo::new(tx.commit("snapshot working copy")); @@ -1482,8 +1484,8 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin if let Some(wc_commit) = &maybe_new_wc_commit { git::reset_head(tx.mut_repo(), &git_repo, wc_commit)?; } - let failed_branches = git::export_refs(tx.mut_repo())?; - print_failed_git_export(ui, &failed_branches)?; + let failed_bookmarks = git::export_refs(tx.mut_repo())?; + print_failed_git_export(ui, &failed_bookmarks)?; } self.user_repo = ReadonlyUserRepo::new(tx.commit(description)); @@ -1667,34 +1669,35 @@ Then run `jj squash` to move the resolution into the conflicted commit."#, Ok(()) } - /// Identifies branches which are eligible to be moved automatically during - /// `jj commit` and `jj new`. Whether a branch is eligible is determined by - /// its target and the user and repo config for "advance-branches". + /// Identifies bookmarks which are eligible to be moved automatically + /// during `jj commit` and `jj new`. Whether a bookmark is eligible is + /// determined by its target and the user and repo config for + /// "advance-bookmarks". /// - /// Returns a Vec of branches in `repo` that point to any of the `from` + /// Returns a Vec of bookmarks in `repo` that point to any of the `from` /// commits and that are eligible to advance. The `from` commits are /// typically the parents of the target commit of `jj commit` or `jj new`. /// /// Branches are not moved until - /// `WorkspaceCommandTransaction::advance_branches()` is called with the - /// `AdvanceableBranch`s returned by this function. + /// `WorkspaceCommandTransaction::advance_bookmarks()` is called with the + /// `AdvanceableBookmark`s returned by this function. /// - /// Returns an empty `std::Vec` if no branches are eligible to advance. - pub fn get_advanceable_branches<'a>( + /// Returns an empty `std::Vec` if no bookmarks are eligible to advance. + pub fn get_advanceable_bookmarks<'a>( &self, from: impl IntoIterator, - ) -> Result, CommandError> { - let ab_settings = AdvanceBranchesSettings::from_config(self.settings.config())?; + ) -> Result, CommandError> { + let ab_settings = AdvanceBookmarksSettings::from_config(self.settings.config())?; if !ab_settings.feature_enabled() { // Return early if we know that there's no work to do. return Ok(Vec::new()); } - let mut advanceable_branches = Vec::new(); + let mut advanceable_bookmarks = Vec::new(); for from_commit in from { - for (name, _) in self.repo().view().local_branches_for_commit(from_commit) { - if ab_settings.branch_is_eligible(name) { - advanceable_branches.push(AdvanceableBranch { + for (name, _) in self.repo().view().local_bookmarks_for_commit(from_commit) { + if ab_settings.bookmark_is_eligible(name) { + advanceable_bookmarks.push(AdvanceableBookmark { name: name.to_owned(), old_commit_id: from_commit.clone(), }); @@ -1702,7 +1705,7 @@ Then run `jj squash` to move the resolution into the conflicted commit."#, } } - Ok(advanceable_branches) + Ok(advanceable_bookmarks) } } @@ -1820,18 +1823,18 @@ impl WorkspaceCommandTransaction<'_> { self.tx } - /// Moves each branch in `branches` from an old commit it's associated with - /// (configured by `get_advanceable_branches`) to the `move_to` commit. If - /// the branch is conflicted before the update, it will remain conflicted - /// after the update, but the conflict will involve the `move_to` commit - /// instead of the old commit. - pub fn advance_branches(&mut self, branches: Vec, move_to: &CommitId) { - for branch in branches { - // This removes the old commit ID from the branch's RefTarget and + /// Moves each bookmark in `bookmarks` from an old commit it's associated + /// with (configured by `get_advanceable_bookmarks`) to the `move_to` + /// commit. If the bookmark is conflicted before the update, it will + /// remain conflicted after the update, but the conflict will involve + /// the `move_to` commit instead of the old commit. + pub fn advance_bookmarks(&mut self, bookmarks: Vec, move_to: &CommitId) { + for bookmark in bookmarks { + // This removes the old commit ID from the bookmark's RefTarget and // replaces it with the `move_to` ID. - self.mut_repo().merge_local_branch( - &branch.name, - &RefTarget::normal(branch.old_commit_id), + self.mut_repo().merge_local_bookmark( + &bookmark.name, + &RefTarget::normal(bookmark.old_commit_id), &RefTarget::normal(move_to.clone()), ); } @@ -2125,35 +2128,35 @@ pub fn print_unmatched_explicit_paths<'a>( Ok(()) } -pub fn print_trackable_remote_branches(ui: &Ui, view: &View) -> io::Result<()> { - let remote_branch_names = view - .branches() - .filter(|(_, branch_target)| branch_target.local_target.is_present()) - .flat_map(|(name, branch_target)| { - branch_target +pub fn print_trackable_remote_bookmarks(ui: &Ui, view: &View) -> io::Result<()> { + let remote_bookmark_names = view + .bookmarks() + .filter(|(_, bookmark_target)| bookmark_target.local_target.is_present()) + .flat_map(|(name, bookmark_target)| { + bookmark_target .remote_refs .into_iter() .filter(|&(_, remote_ref)| !remote_ref.is_tracking()) .map(move |(remote, _)| format!("{name}@{remote}")) }) .collect_vec(); - if remote_branch_names.is_empty() { + if remote_bookmark_names.is_empty() { return Ok(()); } if let Some(mut formatter) = ui.status_formatter() { writeln!( formatter.labeled("hint").with_heading("Hint: "), - "The following remote branches aren't associated with the existing local branches:" + "The following remote bookmarks aren't associated with the existing local bookmarks:" )?; - for full_name in &remote_branch_names { + for full_name in &remote_bookmark_names { write!(formatter, " ")?; - writeln!(formatter.labeled("branch"), "{full_name}")?; + writeln!(formatter.labeled("bookmark"), "{full_name}")?; } writeln!( formatter.labeled("hint").with_heading("Hint: "), - "Run `jj branch track {names}` to keep local branches updated on future pulls.", - names = remote_branch_names.join(" "), + "Run `jj bookmark track {names}` to keep local bookmarks updated on future pulls.", + names = remote_bookmark_names.join(" "), )?; } Ok(()) @@ -2401,30 +2404,30 @@ impl DiffSelector { } #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct RemoteBranchName { - pub branch: String, +pub struct RemoteBookmarkName { + pub bookmark: String, pub remote: String, } -impl fmt::Display for RemoteBranchName { +impl fmt::Display for RemoteBookmarkName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let RemoteBranchName { branch, remote } = self; - write!(f, "{branch}@{remote}") + let RemoteBookmarkName { bookmark, remote } = self; + write!(f, "{bookmark}@{remote}") } } #[derive(Clone, Debug)] -pub struct RemoteBranchNamePattern { - pub branch: StringPattern, +pub struct RemoteBookmarkNamePattern { + pub bookmark: StringPattern, pub remote: StringPattern, } -impl FromStr for RemoteBranchNamePattern { +impl FromStr for RemoteBookmarkNamePattern { type Err = String; fn from_str(src: &str) -> Result { - // The kind prefix applies to both branch and remote fragments. It's - // weird that unanchored patterns like substring:branch@remote is split + // The kind prefix applies to both bookmark and remote fragments. It's + // weird that unanchored patterns like substring:bookmark@remote is split // into two, but I can't think of a better syntax. // TODO: should we disable substring pattern? what if we added regex? let (maybe_kind, pat) = src @@ -2437,27 +2440,27 @@ impl FromStr for RemoteBranchNamePattern { Ok(StringPattern::exact(pat)) } }; - // TODO: maybe reuse revset parser to handle branch/remote name containing @ - let (branch, remote) = pat - .rsplit_once('@') - .ok_or_else(|| "remote branch must be specified in branch@remote form".to_owned())?; - Ok(RemoteBranchNamePattern { - branch: to_pattern(branch)?, + // TODO: maybe reuse revset parser to handle bookmark/remote name containing @ + let (bookmark, remote) = pat.rsplit_once('@').ok_or_else(|| { + "remote bookmark must be specified in bookmark@remote form".to_owned() + })?; + Ok(RemoteBookmarkNamePattern { + bookmark: to_pattern(bookmark)?, remote: to_pattern(remote)?, }) } } -impl RemoteBranchNamePattern { +impl RemoteBookmarkNamePattern { pub fn is_exact(&self) -> bool { - self.branch.is_exact() && self.remote.is_exact() + self.bookmark.is_exact() && self.remote.is_exact() } } -impl fmt::Display for RemoteBranchNamePattern { +impl fmt::Display for RemoteBookmarkNamePattern { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let RemoteBranchNamePattern { branch, remote } = self; - write!(f, "{branch}@{remote}") + let RemoteBookmarkNamePattern { bookmark, remote } = self; + write!(f, "{bookmark}@{remote}") } } diff --git a/cli/src/commands/branch/create.rs b/cli/src/commands/bookmark/create.rs similarity index 62% rename from cli/src/commands/branch/create.rs rename to cli/src/commands/bookmark/create.rs index 16bbbb6af1..2ad1f1491c 100644 --- a/cli/src/commands/branch/create.rs +++ b/cli/src/commands/bookmark/create.rs @@ -16,80 +16,82 @@ use clap::builder::NonEmptyStringValueParser; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::RefTarget; -use super::has_tracked_remote_branches; +use super::has_tracked_remote_bookmarks; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; use crate::command_error::user_error_with_hint; use crate::command_error::CommandError; use crate::ui::Ui; -/// Create a new branch +/// Create a new bookmark #[derive(clap::Args, Clone, Debug)] -pub struct BranchCreateArgs { - /// The branch's target revision +pub struct BookmarkCreateArgs { + /// The bookmark's target revision // // The `--to` alias exists for making it easier for the user to switch - // between `branch create`, `branch move`, and `branch set`. + // between `bookmark create`, `bookmark move`, and `bookmark set`. #[arg(long, short, visible_alias = "to")] revision: Option, - /// The branches to create + /// The bookmarks to create #[arg(required = true, value_parser = NonEmptyStringValueParser::new())] names: Vec, } -pub fn cmd_branch_create( +pub fn cmd_bookmark_create( ui: &mut Ui, command: &CommandHelper, - args: &BranchCreateArgs, + args: &BookmarkCreateArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let target_commit = workspace_command.resolve_single_rev(args.revision.as_ref().unwrap_or(&RevisionArg::AT))?; let view = workspace_command.repo().view(); - let branch_names = &args.names; - for name in branch_names { - if view.get_local_branch(name).is_present() { + let bookmark_names = &args.names; + for name in bookmark_names { + if view.get_local_bookmark(name).is_present() { return Err(user_error_with_hint( - format!("Branch already exists: {name}"), - "Use `jj branch set` to update it.", + format!("Bookmark already exists: {name}"), + "Use `jj bookmark set` to update it.", )); } - if has_tracked_remote_branches(view, name) { + if has_tracked_remote_bookmarks(view, name) { return Err(user_error_with_hint( - format!("Tracked remote branches exist for deleted branch: {name}"), + format!("Tracked remote bookmarks exist for deleted bookmark: {name}"), format!( - "Use `jj branch set` to recreate the local branch. Run `jj branch untrack \ - 'glob:{name}@*'` to disassociate them." + "Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark \ + untrack 'glob:{name}@*'` to disassociate them." ), )); } } 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())); + for bookmark_name in bookmark_names { + tx.mut_repo().set_local_bookmark_target( + bookmark_name, + RefTarget::normal(target_commit.id().clone()), + ); } if let Some(mut formatter) = ui.status_formatter() { write!( formatter, - "Created {} branches pointing to ", - branch_names.len() + "Created {} bookmarks pointing to ", + bookmark_names.len() )?; tx.write_commit_summary(formatter.as_mut(), &target_commit)?; writeln!(formatter)?; } - if branch_names.len() > 1 && args.revision.is_none() { + if bookmark_names.len() > 1 && args.revision.is_none() { writeln!(ui.hint_default(), "Use -r to specify the target revision.")?; } tx.finish( ui, format!( - "create branch {names} pointing to commit {id}", - names = branch_names.join(", "), + "create bookmark {names} pointing to commit {id}", + names = bookmark_names.join(", "), id = target_commit.id().hex() ), )?; diff --git a/cli/src/commands/branch/delete.rs b/cli/src/commands/bookmark/delete.rs similarity index 67% rename from cli/src/commands/branch/delete.rs rename to cli/src/commands/bookmark/delete.rs index def5bff14f..68628ee983 100644 --- a/cli/src/commands/branch/delete.rs +++ b/cli/src/commands/bookmark/delete.rs @@ -16,43 +16,47 @@ use itertools::Itertools as _; use jj_lib::op_store::RefTarget; use jj_lib::str_util::StringPattern; -use super::find_local_branches; +use super::find_local_bookmarks; use crate::cli_util::CommandHelper; use crate::command_error::CommandError; use crate::ui::Ui; -/// Delete an existing branch and propagate the deletion to remotes on the +/// Delete an existing bookmark and propagate the deletion to remotes on the /// next push #[derive(clap::Args, Clone, Debug)] -pub struct BranchDeleteArgs { - /// The branches to delete +pub struct BookmarkDeleteArgs { + /// The bookmarks to delete /// /// By default, the specified name matches exactly. Use `glob:` prefix to - /// select branches by wildcard pattern. For details, see + /// select bookmarks by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(required = true, value_parser = StringPattern::parse)] names: Vec, } -pub fn cmd_branch_delete( +pub fn cmd_bookmark_delete( ui: &mut Ui, command: &CommandHelper, - args: &BranchDeleteArgs, + args: &BookmarkDeleteArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let repo = workspace_command.repo().clone(); - let matched_branches = find_local_branches(repo.view(), &args.names)?; + let matched_bookmarks = find_local_bookmarks(repo.view(), &args.names)?; let mut tx = workspace_command.start_transaction(); - for (name, _) in &matched_branches { + for (name, _) in &matched_bookmarks { tx.mut_repo() - .set_local_branch_target(name, RefTarget::absent()); + .set_local_bookmark_target(name, RefTarget::absent()); } - writeln!(ui.status(), "Deleted {} branches.", matched_branches.len())?; + writeln!( + ui.status(), + "Deleted {} bookmarks.", + matched_bookmarks.len() + )?; tx.finish( ui, format!( - "delete branch {}", - matched_branches.iter().map(|(name, _)| name).join(", ") + "delete bookmark {}", + matched_bookmarks.iter().map(|(name, _)| name).join(", ") ), )?; Ok(()) diff --git a/cli/src/commands/branch/forget.rs b/cli/src/commands/bookmark/forget.rs similarity index 63% rename from cli/src/commands/branch/forget.rs rename to cli/src/commands/bookmark/forget.rs index a27b35a32b..306d557caa 100644 --- a/cli/src/commands/branch/forget.rs +++ b/cli/src/commands/bookmark/forget.rs @@ -19,60 +19,60 @@ use jj_lib::op_store::RemoteRef; use jj_lib::str_util::StringPattern; use jj_lib::view::View; -use super::find_branches_with; +use super::find_bookmarks_with; use crate::cli_util::CommandHelper; use crate::command_error::CommandError; use crate::ui::Ui; -/// Forget everything about a branch, including its local and remote +/// Forget everything about a bookmark, including its local and remote /// targets /// -/// A forgotten branch will not impact remotes on future pushes. It will be +/// A forgotten bookmark 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 +pub struct BookmarkForgetArgs { + /// The bookmarks to forget /// /// By default, the specified name matches exactly. Use `glob:` prefix to - /// select branches by wildcard pattern. For details, see + /// select bookmarks by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(required = true, value_parser = StringPattern::parse)] names: Vec, } -pub fn cmd_branch_forget( +pub fn cmd_bookmark_forget( ui: &mut Ui, command: &CommandHelper, - args: &BranchForgetArgs, + args: &BookmarkForgetArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let repo = workspace_command.repo().clone(); - let matched_branches = find_forgettable_branches(repo.view(), &args.names)?; + let matched_bookmarks = find_forgettable_bookmarks(repo.view(), &args.names)?; let mut tx = workspace_command.start_transaction(); - for (name, branch_target) in &matched_branches { + for (name, bookmark_target) in &matched_bookmarks { tx.mut_repo() - .set_local_branch_target(name, RefTarget::absent()); - for (remote_name, _) in &branch_target.remote_refs { + .set_local_bookmark_target(name, RefTarget::absent()); + for (remote_name, _) in &bookmark_target.remote_refs { tx.mut_repo() - .set_remote_branch(name, remote_name, RemoteRef::absent()); + .set_remote_bookmark(name, remote_name, RemoteRef::absent()); } } - writeln!(ui.status(), "Forgot {} branches.", matched_branches.len())?; + writeln!(ui.status(), "Forgot {} bookmarks.", matched_bookmarks.len())?; tx.finish( ui, format!( - "forget branch {}", - matched_branches.iter().map(|(name, _)| name).join(", ") + "forget bookmark {}", + matched_bookmarks.iter().map(|(name, _)| name).join(", ") ), )?; Ok(()) } -fn find_forgettable_branches<'a>( +fn find_forgettable_bookmarks<'a>( view: &'a View, name_patterns: &[StringPattern], ) -> Result)>, CommandError> { - find_branches_with(name_patterns, |pattern| { - view.branches().filter(|(name, _)| pattern.matches(name)) + find_bookmarks_with(name_patterns, |pattern| { + view.bookmarks().filter(|(name, _)| pattern.matches(name)) }) } diff --git a/cli/src/commands/branch/list.rs b/cli/src/commands/bookmark/list.rs similarity index 66% rename from cli/src/commands/branch/list.rs rename to cli/src/commands/bookmark/list.rs index 786ffca004..c7cc6dbeee 100644 --- a/cli/src/commands/branch/list.rs +++ b/cli/src/commands/bookmark/list.rs @@ -25,48 +25,48 @@ use crate::commit_templater::CommitTemplateLanguage; use crate::commit_templater::RefName; use crate::ui::Ui; -/// List branches and their targets +/// List bookmarks 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 +/// By default, a tracking remote bookmark will be included only if its target +/// is different from the local target. A non-tracking remote bookmark won't be +/// listed. For a conflicted bookmark (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. +/// For information about bookmarks, see +/// https://github.com/martinvonz/jj/blob/main/docs/bookmarks.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 +pub struct BookmarkListArgs { + /// Show all tracking and non-tracking remote bookmarks including the ones + /// whose targets are synchronized with the local bookmarks #[arg(long, short, alias = "all")] all_remotes: bool, - /// Show remote tracked branches only. Omits local Git-tracking branches by - /// default + /// Show remote tracked bookmarks only. Omits local Git-tracking bookmarks + /// by default #[arg(long, short, conflicts_with_all = ["all_remotes"])] tracked: bool, - /// Show conflicted branches only + /// Show conflicted bookmarks only #[arg(long, short, conflicts_with_all = ["all_remotes"])] conflicted: bool, - /// Show branches whose local name matches + /// Show bookmarks whose local name matches /// /// By default, the specified name matches exactly. Use `glob:` prefix to - /// select branches by wildcard pattern. For details, see + /// select bookmarks by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(value_parser = StringPattern::parse)] names: Vec, - /// Show branches whose local targets are in the given revisions + /// Show bookmarks whose local targets are in the given revisions /// - /// Note that `-r deleted_branch` will not work since `deleted_branch` + /// Note that `-r deleted_bookmark` will not work since `deleted_bookmark` /// wouldn't have a local target. #[arg(long, short)] revisions: Vec, - /// Render each branch using the given template + /// Render each bookmark using the given template /// /// All 0-argument methods of the `RefName` type are available as keywords. /// @@ -75,21 +75,21 @@ pub struct BranchListArgs { template: Option, } -pub fn cmd_branch_list( +pub fn cmd_bookmark_list( ui: &mut Ui, command: &CommandHelper, - args: &BranchListArgs, + args: &BookmarkListArgs, ) -> Result<(), CommandError> { let workspace_command = command.workspace_helper(ui)?; let repo = workspace_command.repo(); let view = repo.view(); // Like cmd_git_push(), names and revisions are OR-ed. - let branch_names_to_list = if !args.names.is_empty() || !args.revisions.is_empty() { - let mut branch_names: HashSet<&str> = HashSet::new(); + let bookmark_names_to_list = if !args.names.is_empty() || !args.revisions.is_empty() { + let mut bookmark_names: HashSet<&str> = HashSet::new(); if !args.names.is_empty() { - branch_names.extend( - view.branches() + bookmark_names.extend( + view.bookmarks() .filter(|&(name, _)| args.names.iter().any(|pattern| pattern.matches(name))) .map(|(name, _)| name), ); @@ -97,18 +97,19 @@ pub fn cmd_branch_list( if !args.revisions.is_empty() { // Match against local targets only, which is consistent with "jj git push". let mut expression = workspace_command.parse_union_revsets(&args.revisions)?; - // Intersects with the set of local branch targets to minimize the lookup space. - expression.intersect_with(&RevsetExpression::branches(StringPattern::everything())); + // Intersects with the set of local bookmark targets to minimize the lookup + // space. + expression.intersect_with(&RevsetExpression::bookmarks(StringPattern::everything())); let filtered_targets: HashSet<_> = expression.evaluate_to_commit_ids()?.collect(); - branch_names.extend( - view.local_branches() + bookmark_names.extend( + view.local_bookmarks() .filter(|(_, target)| { target.added_ids().any(|id| filtered_targets.contains(id)) }) .map(|(name, _)| name), ); } - Some(branch_names) + Some(bookmark_names) } else { None }; @@ -117,27 +118,27 @@ pub fn cmd_branch_list( let language = workspace_command.commit_template_language()?; let text = match &args.template { Some(value) => value.to_owned(), - None => command.settings().config().get("templates.branch_list")?, + None => command.settings().config().get("templates.bookmark_list")?, }; workspace_command .parse_template(&language, &text, CommitTemplateLanguage::wrap_ref_name)? - .labeled("branch_list") + .labeled("bookmark_list") }; ui.request_pager(); let mut formatter = ui.stdout_formatter(); - let mut found_deleted_local_branch = false; - let mut found_deleted_tracking_local_branch = false; - let branches_to_list = view.branches().filter(|(name, target)| { - branch_names_to_list + let mut found_deleted_local_bookmark = false; + let mut found_deleted_tracking_local_bookmark = false; + let bookmarks_to_list = view.bookmarks().filter(|(name, target)| { + bookmark_names_to_list .as_ref() - .map_or(true, |branch_names| branch_names.contains(name)) + .map_or(true, |bookmark_names| bookmark_names.contains(name)) && (!args.conflicted || target.local_target.has_conflict()) }); - for (name, branch_target) in branches_to_list { - let local_target = branch_target.local_target; - let remote_refs = branch_target.remote_refs; + for (name, bookmark_target) in bookmarks_to_list { + let local_target = bookmark_target.local_target; + let remote_refs = bookmark_target.remote_refs; let (mut tracking_remote_refs, untracked_remote_refs) = remote_refs .iter() .copied() @@ -165,8 +166,8 @@ pub fn cmd_branch_list( } if local_target.is_absent() && !tracking_remote_refs.is_empty() { - found_deleted_local_branch = true; - found_deleted_tracking_local_branch |= tracking_remote_refs + found_deleted_local_bookmark = true; + found_deleted_tracking_local_bookmark |= tracking_remote_refs .iter() .any(|&(remote, _)| remote != git::REMOTE_NAME_FOR_LOCAL_GIT_REPO); } @@ -182,18 +183,18 @@ pub fn cmd_branch_list( drop(formatter); // Print only one of these hints. It's not important to mention unexported - // branches, but user might wonder why deleted branches are still listed. - if found_deleted_tracking_local_branch { + // bookmarks, but user might wonder why deleted bookmarks are still listed. + if found_deleted_tracking_local_bookmark { writeln!( ui.hint_default(), - "Branches marked as deleted will be *deleted permanently* on the remote on the next \ - `jj git push`. Use `jj branch forget` to prevent this." + "Bookmarkes marked as deleted will be *deleted permanently* on the remote on the next \ + `jj git push`. Use `jj bookmark forget` to prevent this." )?; - } else if found_deleted_local_branch { + } else if found_deleted_local_bookmark { writeln!( ui.hint_default(), - "Branches marked as deleted will be deleted from the underlying Git repo on the next \ - `jj git export`." + "Bookmarkes marked as deleted will be deleted from the underlying Git repo on the \ + next `jj git export`." )?; } diff --git a/cli/src/commands/bookmark/mod.rs b/cli/src/commands/bookmark/mod.rs new file mode 100644 index 0000000000..4b8b44de69 --- /dev/null +++ b/cli/src/commands/bookmark/mod.rs @@ -0,0 +1,240 @@ +// 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. + +mod create; +mod delete; +mod forget; +mod list; +mod r#move; +mod rename; +mod set; +mod track; +mod untrack; + +use itertools::Itertools as _; +use jj_lib::backend::CommitId; +use jj_lib::git; +use jj_lib::op_store::RefTarget; +use jj_lib::op_store::RemoteRef; +use jj_lib::repo::Repo; +use jj_lib::str_util::StringPattern; +use jj_lib::view::View; + +use self::create::cmd_bookmark_create; +use self::create::BookmarkCreateArgs; +use self::delete::cmd_bookmark_delete; +use self::delete::BookmarkDeleteArgs; +use self::forget::cmd_bookmark_forget; +use self::forget::BookmarkForgetArgs; +use self::list::cmd_bookmark_list; +use self::list::BookmarkListArgs; +use self::r#move::cmd_bookmark_move; +use self::r#move::BookmarkMoveArgs; +use self::rename::cmd_bookmark_rename; +use self::rename::BookmarkRenameArgs; +use self::set::cmd_bookmark_set; +use self::set::BookmarkSetArgs; +use self::track::cmd_bookmark_track; +use self::track::BookmarkTrackArgs; +use self::untrack::cmd_bookmark_untrack; +use self::untrack::BookmarkUntrackArgs; +use crate::cli_util::CommandHelper; +use crate::cli_util::RemoteBookmarkName; +use crate::cli_util::RemoteBookmarkNamePattern; +use crate::command_error::user_error; +use crate::command_error::CommandError; +use crate::commands::renamed_cmd; +use crate::ui::Ui; + +/// Manage bookmarks +/// +/// For information about bookmarks, see +/// https://github.com/martinvonz/jj/blob/main/docs/bookmarks.md. +#[derive(clap::Subcommand, Clone, Debug)] +pub enum BookmarkCommand { + #[command(visible_alias("c"))] + Create(BookmarkCreateArgs), + #[command(visible_alias("d"))] + Delete(BookmarkDeleteArgs), + #[command(visible_alias("f"))] + Forget(BookmarkForgetArgs), + #[command(visible_alias("l"))] + List(BookmarkListArgs), + #[command(visible_alias("m"))] + Move(BookmarkMoveArgs), + #[command(visible_alias("r"))] + Rename(BookmarkRenameArgs), + #[command(visible_alias("s"))] + Set(BookmarkSetArgs), + #[command(visible_alias("t"))] + Track(BookmarkTrackArgs), + Untrack(BookmarkUntrackArgs), +} + +pub fn cmd_bookmark( + ui: &mut Ui, + command: &CommandHelper, + subcommand: &BookmarkCommand, +) -> Result<(), CommandError> { + match subcommand { + BookmarkCommand::Create(args) => cmd_bookmark_create(ui, command, args), + BookmarkCommand::Delete(args) => cmd_bookmark_delete(ui, command, args), + BookmarkCommand::Forget(args) => cmd_bookmark_forget(ui, command, args), + BookmarkCommand::List(args) => cmd_bookmark_list(ui, command, args), + BookmarkCommand::Move(args) => cmd_bookmark_move(ui, command, args), + BookmarkCommand::Rename(args) => cmd_bookmark_rename(ui, command, args), + BookmarkCommand::Set(args) => cmd_bookmark_set(ui, command, args), + BookmarkCommand::Track(args) => cmd_bookmark_track(ui, command, args), + BookmarkCommand::Untrack(args) => cmd_bookmark_untrack(ui, command, args), + } +} + +// TODO: Remove in jj 0.27+ +pub fn cmd_branch( + ui: &mut Ui, + command: &CommandHelper, + subcommand: &BookmarkCommand, +) -> Result<(), CommandError> { + type WrappedCmd<'a> = dyn Fn(&mut Ui) -> Result<(), CommandError> + 'a; + let cmd: Box = match subcommand { + BookmarkCommand::Create(args) => Box::new(|ui: &mut Ui| { + renamed_cmd("branch create", "bookmark create", cmd_bookmark_create)(ui, command, args) + }), + BookmarkCommand::Delete(args) => Box::new(|ui| { + renamed_cmd("branch delete", "bookmark delete", cmd_bookmark_delete)(ui, command, args) + }), + BookmarkCommand::Forget(args) => Box::new(|ui| { + renamed_cmd("branch forget", "bookmark forget", cmd_bookmark_forget)(ui, command, args) + }), + BookmarkCommand::List(args) => Box::new(|ui| { + renamed_cmd("branch list", "bookmark list", cmd_bookmark_list)(ui, command, args) + }), + BookmarkCommand::Move(args) => Box::new(|ui| { + renamed_cmd("branch move", "bookmark move", cmd_bookmark_move)(ui, command, args) + }), + BookmarkCommand::Rename(args) => Box::new(|ui| { + renamed_cmd("branch rename", "bookmark rename", cmd_bookmark_rename)(ui, command, args) + }), + BookmarkCommand::Set(args) => Box::new(|ui| { + renamed_cmd("branch set", "bookmark set", cmd_bookmark_set)(ui, command, args) + }), + BookmarkCommand::Track(args) => Box::new(|ui| { + renamed_cmd("branch track", "bookmark track", cmd_bookmark_track)(ui, command, args) + }), + BookmarkCommand::Untrack(args) => Box::new(|ui| { + renamed_cmd("branch untrack", "bookmark untrack", cmd_bookmark_untrack)( + ui, command, args, + ) + }), + }; + cmd(ui) +} + +fn find_local_bookmarks<'a>( + view: &'a View, + name_patterns: &[StringPattern], +) -> Result, CommandError> { + find_bookmarks_with(name_patterns, |pattern| { + view.local_bookmarks_matching(pattern) + }) +} + +fn find_bookmarks_with<'a, 'b, V, I: Iterator>( + name_patterns: &'b [StringPattern], + mut find_matches: impl FnMut(&'b StringPattern) -> I, +) -> Result, CommandError> { + let mut matching_bookmarks: Vec = vec![]; + let mut unmatched_patterns = vec![]; + for pattern in name_patterns { + let mut matches = find_matches(pattern).peekable(); + if matches.peek().is_none() { + unmatched_patterns.push(pattern); + } + matching_bookmarks.extend(matches); + } + match &unmatched_patterns[..] { + [] => { + matching_bookmarks.sort_unstable_by_key(|(name, _)| *name); + matching_bookmarks.dedup_by_key(|(name, _)| *name); + Ok(matching_bookmarks) + } + [pattern] if pattern.is_exact() => Err(user_error(format!("No such bookmark: {pattern}"))), + patterns => Err(user_error(format!( + "No matching bookmarks for patterns: {}", + patterns.iter().join(", ") + ))), + } +} + +fn find_remote_bookmarks<'a>( + view: &'a View, + name_patterns: &[RemoteBookmarkNamePattern], +) -> Result, CommandError> { + let mut matching_bookmarks = vec![]; + let mut unmatched_patterns = vec![]; + for pattern in name_patterns { + let mut matches = view + .remote_bookmarks_matching(&pattern.bookmark, &pattern.remote) + .map(|((bookmark, remote), remote_ref)| { + let name = RemoteBookmarkName { + bookmark: bookmark.to_owned(), + remote: remote.to_owned(), + }; + (name, remote_ref) + }) + .peekable(); + if matches.peek().is_none() { + unmatched_patterns.push(pattern); + } + matching_bookmarks.extend(matches); + } + match &unmatched_patterns[..] { + [] => { + matching_bookmarks.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); + matching_bookmarks.dedup_by(|(name1, _), (name2, _)| name1 == name2); + Ok(matching_bookmarks) + } + [pattern] if pattern.is_exact() => { + Err(user_error(format!("No such remote bookmark: {pattern}"))) + } + patterns => Err(user_error(format!( + "No matching remote bookmarks for patterns: {}", + patterns.iter().join(", ") + ))), + } +} + +/// Whether or not the `bookmark` has any tracked remotes (i.e. is a tracking +/// local bookmark.) +fn has_tracked_remote_bookmarks(view: &View, bookmark: &str) -> bool { + view.remote_bookmarks_matching( + &StringPattern::exact(bookmark), + &StringPattern::everything(), + ) + .filter(|&((_, remote_name), _)| remote_name != git::REMOTE_NAME_FOR_LOCAL_GIT_REPO) + .any(|(_, remote_ref)| remote_ref.is_tracking()) +} + +fn is_fast_forward(repo: &dyn Repo, old_target: &RefTarget, new_target_id: &CommitId) -> bool { + if old_target.is_present() { + // Strictly speaking, "all" old targets should be ancestors, but we allow + // conflict resolution by setting bookmark to "any" of the old target + // descendants. + old_target + .added_ids() + .any(|old| repo.index().is_ancestor(old, new_target_id)) + } else { + true + } +} diff --git a/cli/src/commands/branch/move.rs b/cli/src/commands/bookmark/move.rs similarity index 63% rename from cli/src/commands/branch/move.rs rename to cli/src/commands/bookmark/move.rs index 9f5443e5a7..090384700a 100644 --- a/cli/src/commands/branch/move.rs +++ b/cli/src/commands/bookmark/move.rs @@ -18,7 +18,7 @@ use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::RefTarget; use jj_lib::str_util::StringPattern; -use super::find_branches_with; +use super::find_bookmarks_with; use super::is_fast_forward; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; @@ -26,51 +26,52 @@ use crate::command_error::user_error_with_hint; use crate::command_error::CommandError; use crate::ui::Ui; -/// Move existing branches to target revision +/// Move existing bookmarks to target revision /// -/// If branch names are given, the specified branches will be updated to point -/// to the target revision. +/// If bookmark names are given, the specified bookmarks will be updated to +/// point to the target revision. /// -/// If `--from` options are given, branches currently pointing to the specified -/// revisions will be updated. The branches can also be filtered by names. +/// If `--from` options are given, bookmarks currently pointing to the +/// specified revisions will be updated. The bookmarks can also be filtered by +/// names. /// -/// Example: pull up the nearest branches to the working-copy parent +/// Example: pull up the nearest bookmarks to the working-copy parent /// -/// $ jj branch move --from 'heads(::@- & branches())' --to @- +/// $ jj bookmark move --from 'heads(::@- & bookmarks())' --to @- #[derive(clap::Args, Clone, Debug)] #[command(group(clap::ArgGroup::new("source").multiple(true).required(true)))] -pub struct BranchMoveArgs { - /// Move branches from the given revisions +pub struct BookmarkMoveArgs { + /// Move bookmarks from the given revisions #[arg(long, group = "source", value_name = "REVISIONS")] from: Vec, - /// Move branches to this revision + /// Move bookmarks to this revision #[arg(long, default_value = "@", value_name = "REVISION")] to: RevisionArg, - /// Allow moving branches backwards or sideways + /// Allow moving bookmarks backwards or sideways #[arg(long, short = 'B')] allow_backwards: bool, - /// Move branches matching the given name patterns + /// Move bookmarks matching the given name patterns /// /// By default, the specified name matches exactly. Use `glob:` prefix to - /// select branches by wildcard pattern. For details, see + /// select bookmarks by wildcard pattern. For details, see /// https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. #[arg(group = "source", value_parser = StringPattern::parse)] names: Vec, } -pub fn cmd_branch_move( +pub fn cmd_bookmark_move( ui: &mut Ui, command: &CommandHelper, - args: &BranchMoveArgs, + args: &BookmarkMoveArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let repo = workspace_command.repo().clone(); let target_commit = workspace_command.resolve_single_rev(&args.to)?; - let matched_branches = { + let matched_bookmarks = { let is_source_commit = if !args.from.is_empty() { workspace_command .parse_union_revsets(&args.from)? @@ -79,63 +80,63 @@ pub fn cmd_branch_move( } else { Box::new(|_: &CommitId| true) }; - let mut branches = if !args.names.is_empty() { - find_branches_with(&args.names, |pattern| { + let mut bookmarks = if !args.names.is_empty() { + find_bookmarks_with(&args.names, |pattern| { repo.view() - .local_branches_matching(pattern) + .local_bookmarks_matching(pattern) .filter(|(_, target)| target.added_ids().any(&is_source_commit)) })? } else { repo.view() - .local_branches() + .local_bookmarks() .filter(|(_, target)| target.added_ids().any(&is_source_commit)) .collect() }; // Noop matches aren't error, but should be excluded from stats. - branches.retain(|(_, old_target)| old_target.as_normal() != Some(target_commit.id())); - branches + bookmarks.retain(|(_, old_target)| old_target.as_normal() != Some(target_commit.id())); + bookmarks }; - if matched_branches.is_empty() { - writeln!(ui.status(), "No branches to update.")?; + if matched_bookmarks.is_empty() { + writeln!(ui.status(), "No bookmarks to update.")?; return Ok(()); } if !args.allow_backwards { - if let Some((name, _)) = matched_branches + if let Some((name, _)) = matched_bookmarks .iter() .find(|(_, old_target)| !is_fast_forward(repo.as_ref(), old_target, target_commit.id())) { return Err(user_error_with_hint( - format!("Refusing to move branch backwards or sideways: {name}"), + format!("Refusing to move bookmark backwards or sideways: {name}"), "Use --allow-backwards to allow it.", )); } } let mut tx = workspace_command.start_transaction(); - for (name, _) in &matched_branches { + for (name, _) in &matched_bookmarks { tx.mut_repo() - .set_local_branch_target(name, RefTarget::normal(target_commit.id().clone())); + .set_local_bookmark_target(name, RefTarget::normal(target_commit.id().clone())); } if let Some(mut formatter) = ui.status_formatter() { - write!(formatter, "Moved {} branches to ", matched_branches.len())?; + write!(formatter, "Moved {} bookmarks to ", matched_bookmarks.len())?; tx.write_commit_summary(formatter.as_mut(), &target_commit)?; writeln!(formatter)?; } - if matched_branches.len() > 1 && args.names.is_empty() { + if matched_bookmarks.len() > 1 && args.names.is_empty() { writeln!( ui.hint_default(), - "Specify branch by name to update just one of the branches." + "Specify bookmark by name to update just one of the bookmarks." )?; } tx.finish( ui, format!( - "point branch {names} to commit {id}", - names = matched_branches.iter().map(|(name, _)| name).join(", "), + "point bookmark {names} to commit {id}", + names = matched_bookmarks.iter().map(|(name, _)| name).join(", "), id = target_commit.id().hex() ), )?; diff --git a/cli/src/commands/bookmark/rename.rs b/cli/src/commands/bookmark/rename.rs new file mode 100644 index 0000000000..0f8c8435f1 --- /dev/null +++ b/cli/src/commands/bookmark/rename.rs @@ -0,0 +1,93 @@ +// 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 jj_lib::op_store::RefTarget; + +use super::has_tracked_remote_bookmarks; +use crate::cli_util::CommandHelper; +use crate::command_error::user_error; +use crate::command_error::CommandError; +use crate::ui::Ui; + +/// Rename `old` bookmark name to `new` bookmark name +/// +/// The new bookmark name points at the same commit as the old bookmark name. +#[derive(clap::Args, Clone, Debug)] +pub struct BookmarkRenameArgs { + /// The old name of the bookmark + old: String, + + /// The new name of the bookmark + new: String, +} + +pub fn cmd_bookmark_rename( + ui: &mut Ui, + command: &CommandHelper, + args: &BookmarkRenameArgs, +) -> Result<(), CommandError> { + let mut workspace_command = command.workspace_helper(ui)?; + let view = workspace_command.repo().view(); + let old_bookmark = &args.old; + let ref_target = view.get_local_bookmark(old_bookmark).clone(); + if ref_target.is_absent() { + return Err(user_error(format!("No such bookmark: {old_bookmark}"))); + } + + let new_bookmark = &args.new; + if view.get_local_bookmark(new_bookmark).is_present() { + return Err(user_error(format!( + "Bookmark already exists: {new_bookmark}" + ))); + } + + let mut tx = workspace_command.start_transaction(); + tx.mut_repo() + .set_local_bookmark_target(new_bookmark, ref_target); + tx.mut_repo() + .set_local_bookmark_target(old_bookmark, RefTarget::absent()); + tx.finish( + ui, + format!("rename bookmark {old_bookmark} to {new_bookmark}"), + )?; + + let view = workspace_command.repo().view(); + if has_tracked_remote_bookmarks(view, old_bookmark) { + writeln!( + ui.warning_default(), + "Tracked remote bookmarks for bookmark {old_bookmark} were not renamed.", + )?; + writeln!( + ui.hint_default(), + "To rename the bookmark on the remote, you can `jj git push --bookmark \ + {old_bookmark}` first (to delete it on the remote), and then `jj git push --bookmark \ + {new_bookmark}`. `jj git push --all` would also be sufficient." + )?; + } + if has_tracked_remote_bookmarks(view, new_bookmark) { + // This isn't an error because bookmark renaming can't be propagated to + // the remote immediately. "rename old new && rename new old" should be + // allowed even if the original old bookmark had tracked remotes. + writeln!( + ui.warning_default(), + "Tracked remote bookmarks for bookmark {new_bookmark} exist." + )?; + writeln!( + ui.hint_default(), + "Run `jj bookmark untrack 'glob:{new_bookmark}@*'` to disassociate them." + )?; + } + + Ok(()) +} diff --git a/cli/src/commands/branch/set.rs b/cli/src/commands/bookmark/set.rs similarity index 61% rename from cli/src/commands/branch/set.rs rename to cli/src/commands/bookmark/set.rs index 2733d77dcf..f1b2caef41 100644 --- a/cli/src/commands/branch/set.rs +++ b/cli/src/commands/bookmark/set.rs @@ -16,7 +16,7 @@ use clap::builder::NonEmptyStringValueParser; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::RefTarget; -use super::has_tracked_remote_branches; +use super::has_tracked_remote_bookmarks; use super::is_fast_forward; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; @@ -24,88 +24,90 @@ use crate::command_error::user_error_with_hint; use crate::command_error::CommandError; use crate::ui::Ui; -/// Create or update a branch to point to a certain commit +/// Create or update a bookmark to point to a certain commit #[derive(clap::Args, Clone, Debug)] -pub struct BranchSetArgs { - /// The branch's target revision +pub struct BookmarkSetArgs { + /// The bookmark's target revision #[arg(long, short, visible_alias = "to")] revision: Option, - /// Allow moving the branch backwards or sideways + /// Allow moving the bookmark backwards or sideways #[arg(long, short = 'B')] allow_backwards: bool, - /// The branches to update + /// The bookmarks to update #[arg(required = true, value_parser = NonEmptyStringValueParser::new())] names: Vec, } -pub fn cmd_branch_set( +pub fn cmd_bookmark_set( ui: &mut Ui, command: &CommandHelper, - args: &BranchSetArgs, + args: &BookmarkSetArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let target_commit = workspace_command.resolve_single_rev(args.revision.as_ref().unwrap_or(&RevisionArg::AT))?; let repo = workspace_command.repo().as_ref(); - let branch_names = &args.names; - let mut new_branch_count = 0; - let mut moved_branch_count = 0; - for name in branch_names { - let old_target = repo.view().get_local_branch(name); - // If a branch is absent locally but is still tracking remote branches, - // we are resurrecting the local branch, not "creating" a new branch. - if old_target.is_absent() && !has_tracked_remote_branches(repo.view(), name) { - new_branch_count += 1; + let bookmark_names = &args.names; + let mut new_bookmark_count = 0; + let mut moved_bookmark_count = 0; + for name in bookmark_names { + let old_target = repo.view().get_local_bookmark(name); + // If a bookmark is absent locally but is still tracking remote bookmarks, + // we are resurrecting the local bookmark, not "creating" a new bookmark. + if old_target.is_absent() && !has_tracked_remote_bookmarks(repo.view(), name) { + new_bookmark_count += 1; } else if old_target.as_normal() != Some(target_commit.id()) { - moved_branch_count += 1; + moved_bookmark_count += 1; } if !args.allow_backwards && !is_fast_forward(repo, old_target, target_commit.id()) { return Err(user_error_with_hint( - format!("Refusing to move branch backwards or sideways: {name}"), + format!("Refusing to move bookmark backwards or sideways: {name}"), "Use --allow-backwards to allow it.", )); } } 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())); + for bookmark_name in bookmark_names { + tx.mut_repo().set_local_bookmark_target( + bookmark_name, + RefTarget::normal(target_commit.id().clone()), + ); } if let Some(mut formatter) = ui.status_formatter() { - if new_branch_count > 0 { + if new_bookmark_count > 0 { write!( formatter, - "Created {new_branch_count} branches pointing to " + "Created {new_bookmark_count} bookmarks pointing to " )?; tx.write_commit_summary(formatter.as_mut(), &target_commit)?; writeln!(formatter)?; } - if moved_branch_count > 0 { - write!(formatter, "Moved {moved_branch_count} branches to ")?; + if moved_bookmark_count > 0 { + write!(formatter, "Moved {moved_bookmark_count} bookmarks to ")?; tx.write_commit_summary(formatter.as_mut(), &target_commit)?; writeln!(formatter)?; } } - if branch_names.len() > 1 && args.revision.is_none() { + if bookmark_names.len() > 1 && args.revision.is_none() { writeln!(ui.hint_default(), "Use -r to specify the target revision.")?; } - if new_branch_count > 0 { + if new_bookmark_count > 0 { // TODO: delete this hint in jj 0.25+ writeln!( ui.hint_default(), - "Consider using `jj branch move` if your intention was to move existing branches." + "Consider using `jj bookmark move` if your intention was to move existing bookmarks." )?; } tx.finish( ui, format!( - "point branch {names} to commit {id}", - names = branch_names.join(", "), + "point bookmark {names} to commit {id}", + names = bookmark_names.join(", "), id = target_commit.id().hex() ), )?; diff --git a/cli/src/commands/branch/track.rs b/cli/src/commands/bookmark/track.rs similarity index 62% rename from cli/src/commands/branch/track.rs rename to cli/src/commands/bookmark/track.rs index a931964293..51a4f00a42 100644 --- a/cli/src/commands/branch/track.rs +++ b/cli/src/commands/bookmark/track.rs @@ -16,45 +16,45 @@ use std::collections::HashMap; use itertools::Itertools as _; -use super::find_remote_branches; +use super::find_remote_bookmarks; use crate::cli_util::CommandHelper; -use crate::cli_util::RemoteBranchNamePattern; +use crate::cli_util::RemoteBookmarkNamePattern; use crate::command_error::CommandError; use crate::commit_templater::CommitTemplateLanguage; use crate::commit_templater::RefName; use crate::ui::Ui; -/// Start tracking given remote branches +/// Start tracking given remote bookmarks /// -/// 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 +/// A tracking remote bookmark will be imported as a local bookmark of the same +/// name. Changes to it will propagate to the existing local bookmark on future /// pulls. #[derive(clap::Args, Clone, Debug)] -pub struct BranchTrackArgs { - /// Remote branches to track +pub struct BookmarkTrackArgs { + /// Remote bookmarks to track /// /// By default, the specified name matches exactly. Use `glob:` prefix to - /// select branches by wildcard pattern. For details, see + /// select bookmarks 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 + /// Examples: bookmark@remote, glob:main@*, glob:jjfan-*@upstream #[arg(required = true, value_name = "BRANCH@REMOTE")] - names: Vec, + names: Vec, } -pub fn cmd_branch_track( +pub fn cmd_bookmark_track( ui: &mut Ui, command: &CommandHelper, - args: &BranchTrackArgs, + args: &BookmarkTrackArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let view = workspace_command.repo().view(); let mut names = Vec::new(); - for (name, remote_ref) in find_remote_branches(view, &args.names)? { + for (name, remote_ref) in find_remote_bookmarks(view, &args.names)? { if remote_ref.is_tracking() { writeln!( ui.warning_default(), - "Remote branch already tracked: {name}" + "Remote bookmark already tracked: {name}" )?; } else { names.push(name); @@ -63,21 +63,21 @@ pub fn cmd_branch_track( let mut tx = workspace_command.start_transaction(); for name in &names { tx.mut_repo() - .track_remote_branch(&name.branch, &name.remote); + .track_remote_bookmark(&name.bookmark, &name.remote); } if !names.is_empty() { writeln!( ui.status(), - "Started tracking {} remote branches.", + "Started tracking {} remote bookmarks.", names.len() )?; } tx.finish( ui, - format!("track remote branch {}", names.iter().join(", ")), + format!("track remote bookmark {}", names.iter().join(", ")), )?; - //show conflicted branches if there are some + //show conflicted bookmarks if there are some if let Some(mut formatter) = ui.status_formatter() { let template = { @@ -85,39 +85,39 @@ pub fn cmd_branch_track( let text = command .settings() .config() - .get::("templates.branch_list")?; + .get::("templates.bookmark_list")?; workspace_command .parse_template(&language, &text, CommitTemplateLanguage::wrap_ref_name)? - .labeled("branch_list") + .labeled("bookmark_list") }; - let mut remote_per_branch: HashMap<&str, Vec<&str>> = HashMap::new(); + let mut remote_per_bookmark: HashMap<&str, Vec<&str>> = HashMap::new(); for n in names.iter() { - remote_per_branch - .entry(&n.branch) + remote_per_bookmark + .entry(&n.bookmark) .or_default() .push(&n.remote); } - let branches_to_list = + let bookmarks_to_list = workspace_command .repo() .view() - .branches() + .bookmarks() .filter(|(name, target)| { - remote_per_branch.contains_key(name) && target.local_target.has_conflict() + remote_per_bookmark.contains_key(name) && target.local_target.has_conflict() }); - for (name, branch_target) in branches_to_list { - let local_target = branch_target.local_target; + for (name, bookmark_target) in bookmarks_to_list { + let local_target = bookmark_target.local_target; let ref_name = RefName::local( name, local_target.clone(), - branch_target.remote_refs.iter().map(|x| x.1), + bookmark_target.remote_refs.iter().map(|x| x.1), ); template.format(&ref_name, formatter.as_mut())?; - for (remote_name, remote_ref) in branch_target.remote_refs { - if remote_per_branch[name].contains(&remote_name) { + for (remote_name, remote_ref) in bookmark_target.remote_refs { + if remote_per_bookmark[name].contains(&remote_name) { let ref_name = RefName::remote(name, remote_name, remote_ref.clone(), local_target); template.format(&ref_name, formatter.as_mut())?; diff --git a/cli/src/commands/branch/untrack.rs b/cli/src/commands/bookmark/untrack.rs similarity index 65% rename from cli/src/commands/branch/untrack.rs rename to cli/src/commands/bookmark/untrack.rs index d722b2deed..b99a517fb0 100644 --- a/cli/src/commands/branch/untrack.rs +++ b/cli/src/commands/bookmark/untrack.rs @@ -15,48 +15,49 @@ use itertools::Itertools as _; use jj_lib::git; -use super::find_remote_branches; +use super::find_remote_bookmarks; use crate::cli_util::CommandHelper; -use crate::cli_util::RemoteBranchNamePattern; +use crate::cli_util::RemoteBookmarkNamePattern; use crate::command_error::CommandError; use crate::ui::Ui; -/// Stop tracking given remote branches +/// Stop tracking given remote bookmarks /// -/// 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. +/// A non-tracking remote bookmark is just a pointer to the last-fetched remote +/// bookmark. It won't be imported as a local bookmark on future pulls. #[derive(clap::Args, Clone, Debug)] -pub struct BranchUntrackArgs { - /// Remote branches to untrack +pub struct BookmarkUntrackArgs { + /// Remote bookmarks to untrack /// /// By default, the specified name matches exactly. Use `glob:` prefix to - /// select branches by wildcard pattern. For details, see + /// select bookmarks 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 + /// Examples: bookmark@remote, glob:main@*, glob:jjfan-*@upstream #[arg(required = true, value_name = "BRANCH@REMOTE")] - names: Vec, + names: Vec, } -pub fn cmd_branch_untrack( +pub fn cmd_bookmark_untrack( ui: &mut Ui, command: &CommandHelper, - args: &BranchUntrackArgs, + args: &BookmarkUntrackArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let view = workspace_command.repo().view(); let mut names = Vec::new(); - for (name, remote_ref) in find_remote_branches(view, &args.names)? { + for (name, remote_ref) in find_remote_bookmarks(view, &args.names)? { if name.remote == git::REMOTE_NAME_FOR_LOCAL_GIT_REPO { - // This restriction can be lifted if we want to support untracked @git branches. + // This restriction can be lifted if we want to support untracked @git + // bookmarks. writeln!( ui.warning_default(), - "Git-tracking branch cannot be untracked: {name}" + "Git-tracking bookmark cannot be untracked: {name}" )?; } else if !remote_ref.is_tracking() { writeln!( ui.warning_default(), - "Remote branch not tracked yet: {name}" + "Remote bookmark not tracked yet: {name}" )?; } else { names.push(name); @@ -65,18 +66,18 @@ pub fn cmd_branch_untrack( let mut tx = workspace_command.start_transaction(); for name in &names { tx.mut_repo() - .untrack_remote_branch(&name.branch, &name.remote); + .untrack_remote_bookmark(&name.bookmark, &name.remote); } if !names.is_empty() { writeln!( ui.status(), - "Stopped tracking {} remote branches.", + "Stopped tracking {} remote bookmarks.", names.len() )?; } tx.finish( ui, - format!("untrack remote branch {}", names.iter().join(", ")), + format!("untrack remote bookmark {}", names.iter().join(", ")), )?; Ok(()) } diff --git a/cli/src/commands/branch/mod.rs b/cli/src/commands/branch/mod.rs deleted file mode 100644 index 636f787b04..0000000000 --- a/cli/src/commands/branch/mod.rs +++ /dev/null @@ -1,194 +0,0 @@ -// 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. - -mod create; -mod delete; -mod forget; -mod list; -mod r#move; -mod rename; -mod set; -mod track; -mod untrack; - -use itertools::Itertools as _; -use jj_lib::backend::CommitId; -use jj_lib::git; -use jj_lib::op_store::RefTarget; -use jj_lib::op_store::RemoteRef; -use jj_lib::repo::Repo; -use jj_lib::str_util::StringPattern; -use jj_lib::view::View; - -use self::create::cmd_branch_create; -use self::create::BranchCreateArgs; -use self::delete::cmd_branch_delete; -use self::delete::BranchDeleteArgs; -use self::forget::cmd_branch_forget; -use self::forget::BranchForgetArgs; -use self::list::cmd_branch_list; -use self::list::BranchListArgs; -use self::r#move::cmd_branch_move; -use self::r#move::BranchMoveArgs; -use self::rename::cmd_branch_rename; -use self::rename::BranchRenameArgs; -use self::set::cmd_branch_set; -use self::set::BranchSetArgs; -use self::track::cmd_branch_track; -use self::track::BranchTrackArgs; -use self::untrack::cmd_branch_untrack; -use self::untrack::BranchUntrackArgs; -use crate::cli_util::CommandHelper; -use crate::cli_util::RemoteBranchName; -use crate::cli_util::RemoteBranchNamePattern; -use crate::command_error::user_error; -use crate::command_error::CommandError; -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("m"))] - Move(BranchMoveArgs), - #[command(visible_alias("r"))] - Rename(BranchRenameArgs), - #[command(visible_alias("s"))] - Set(BranchSetArgs), - #[command(visible_alias("t"))] - Track(BranchTrackArgs), - Untrack(BranchUntrackArgs), -} - -pub fn cmd_branch( - ui: &mut Ui, - command: &CommandHelper, - subcommand: &BranchCommand, -) -> Result<(), CommandError> { - match subcommand { - BranchCommand::Create(args) => cmd_branch_create(ui, command, args), - BranchCommand::Delete(args) => cmd_branch_delete(ui, command, args), - BranchCommand::Forget(args) => cmd_branch_forget(ui, command, args), - BranchCommand::List(args) => cmd_branch_list(ui, command, args), - BranchCommand::Move(args) => cmd_branch_move(ui, command, args), - BranchCommand::Rename(args) => cmd_branch_rename(ui, command, args), - BranchCommand::Set(args) => cmd_branch_set(ui, command, args), - BranchCommand::Track(args) => cmd_branch_track(ui, command, args), - BranchCommand::Untrack(args) => cmd_branch_untrack(ui, command, args), - } -} - -fn find_local_branches<'a>( - view: &'a View, - name_patterns: &[StringPattern], -) -> Result, CommandError> { - find_branches_with(name_patterns, |pattern| { - view.local_branches_matching(pattern) - }) -} - -fn find_branches_with<'a, 'b, V, I: Iterator>( - name_patterns: &'b [StringPattern], - mut find_matches: impl FnMut(&'b StringPattern) -> I, -) -> Result, CommandError> { - let mut matching_branches: Vec = vec![]; - let mut unmatched_patterns = vec![]; - for pattern in name_patterns { - let mut matches = find_matches(pattern).peekable(); - if matches.peek().is_none() { - unmatched_patterns.push(pattern); - } - matching_branches.extend(matches); - } - match &unmatched_patterns[..] { - [] => { - matching_branches.sort_unstable_by_key(|(name, _)| *name); - matching_branches.dedup_by_key(|(name, _)| *name); - 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, 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(", ") - ))), - } -} - -/// Whether or not the `branch` has any tracked remotes (i.e. is a tracking -/// local branch.) -fn has_tracked_remote_branches(view: &View, branch: &str) -> bool { - view.remote_branches_matching(&StringPattern::exact(branch), &StringPattern::everything()) - .filter(|&((_, remote_name), _)| remote_name != git::REMOTE_NAME_FOR_LOCAL_GIT_REPO) - .any(|(_, remote_ref)| remote_ref.is_tracking()) -} - -fn is_fast_forward(repo: &dyn Repo, old_target: &RefTarget, new_target_id: &CommitId) -> bool { - if old_target.is_present() { - // 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, new_target_id)) - } else { - true - } -} diff --git a/cli/src/commands/branch/rename.rs b/cli/src/commands/branch/rename.rs deleted file mode 100644 index 198b51cabb..0000000000 --- a/cli/src/commands/branch/rename.rs +++ /dev/null @@ -1,88 +0,0 @@ -// 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 jj_lib::op_store::RefTarget; - -use super::has_tracked_remote_branches; -use crate::cli_util::CommandHelper; -use crate::command_error::user_error; -use crate::command_error::CommandError; -use crate::ui::Ui; - -/// 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 - old: String, - - /// The new name of the branch - new: String, -} - -pub 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 branch {old_branch} to {new_branch}"))?; - - let view = workspace_command.repo().view(); - if has_tracked_remote_branches(view, old_branch) { - writeln!( - ui.warning_default(), - "Tracked remote branches for branch {old_branch} were not renamed.", - )?; - writeln!( - ui.hint_default(), - "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." - )?; - } - if has_tracked_remote_branches(view, new_branch) { - // This isn't an error because branch renaming can't be propagated to - // the remote immediately. "rename old new && rename new old" should be - // allowed even if the original old branch had tracked remotes. - writeln!( - ui.warning_default(), - "Tracked remote branches for branch {new_branch} exist." - )?; - writeln!( - ui.hint_default(), - "Run `jj branch untrack 'glob:{new_branch}@*'` to disassociate them." - )?; - } - - Ok(()) -} diff --git a/cli/src/commands/commit.rs b/cli/src/commands/commit.rs index f810440c11..311832d88d 100644 --- a/cli/src/commands/commit.rs +++ b/cli/src/commands/commit.rs @@ -67,7 +67,7 @@ pub(crate) fn cmd_commit( let matcher = workspace_command .parse_file_patterns(&args.paths)? .to_matcher(); - let advanceable_branches = workspace_command.get_advanceable_branches(commit.parent_ids())?; + let advanceable_branches = workspace_command.get_advanceable_bookmarks(commit.parent_ids())?; let diff_selector = workspace_command.diff_selector(ui, args.tool.as_deref(), args.interactive)?; let mut tx = workspace_command.start_transaction(); @@ -135,7 +135,7 @@ new working-copy commit. .write()?; // Does nothing if there's no branches to advance. - tx.advance_branches(advanceable_branches, new_commit.id()); + tx.advance_bookmarks(advanceable_branches, new_commit.id()); for workspace_id in workspace_ids { tx.mut_repo().edit(workspace_id, &new_wc_commit).unwrap(); diff --git a/cli/src/commands/git/clone.rs b/cli/src/commands/git/clone.rs index 15bbb311bc..5ce949a1f6 100644 --- a/cli/src/commands/git/clone.rs +++ b/cli/src/commands/git/clone.rs @@ -156,29 +156,29 @@ pub fn cmd_git_clone( } let (mut workspace_command, stats) = clone_result?; - if let Some(default_branch) = &stats.default_branch { + if let Some(default_bookmark) = &stats.default_bookmark { // Set repository level `trunk()` alias to the default remote branch. let config_path = workspace_command.repo().repo_path().join("config.toml"); write_config_value_to_file( &ConfigNamePathBuf::from_iter(["revset-aliases", "trunk()"]), - format!("{default_branch}@{remote_name}").into(), + format!("{default_bookmark}@{remote_name}").into(), &config_path, )?; writeln!( ui.status(), - "Setting the revset alias \"trunk()\" to \"{default_branch}@{remote_name}\"" + "Setting the revset alias \"trunk()\" to \"{default_bookmark}@{remote_name}\"" )?; let default_branch_remote_ref = workspace_command .repo() .view() - .get_remote_branch(default_branch, remote_name); + .get_remote_bookmark(default_bookmark, remote_name); if let Some(commit_id) = default_branch_remote_ref.target.as_normal().cloned() { let mut checkout_tx = workspace_command.start_transaction(); // For convenience, create local branch as Git would do. checkout_tx .mut_repo() - .track_remote_branch(default_branch, remote_name); + .track_remote_bookmark(default_bookmark, remote_name); if let Ok(commit) = checkout_tx.repo().store().get_commit(&commit_id) { checkout_tx.check_out(&commit)?; } diff --git a/cli/src/commands/git/init.rs b/cli/src/commands/git/init.rs index 824802c1ab..aafadc7b84 100644 --- a/cli/src/commands/git/init.rs +++ b/cli/src/commands/git/init.rs @@ -25,7 +25,7 @@ use jj_lib::repo::ReadonlyRepo; use jj_lib::repo::Repo; use jj_lib::workspace::Workspace; -use crate::cli_util::print_trackable_remote_branches; +use crate::cli_util::print_trackable_remote_bookmarks; use crate::cli_util::start_repo_transaction; use crate::cli_util::CommandHelper; use crate::command_error::cli_error; @@ -183,7 +183,7 @@ pub fn do_init( tx.finish(ui, "import git head")?; } } - print_trackable_remote_branches(ui, workspace_command.repo().view())?; + print_trackable_remote_bookmarks(ui, workspace_command.repo().view())?; } GitInitMode::Internal => { Workspace::init_internal_git(command.settings(), workspace_root)?; @@ -240,7 +240,7 @@ pub fn maybe_set_repository_level_trunk_alias( if let Ok(reference) = git_repo.find_reference("refs/remotes/origin/HEAD") { if let Some(reference_name) = reference.symbolic_target() { if let Some(RefName::RemoteBranch { - branch: default_branch, + bookmark: default_branch, .. }) = parse_git_ref(reference_name) { diff --git a/cli/src/commands/git/push.rs b/cli/src/commands/git/push.rs index 3e7448f55e..e1b3d56939 100644 --- a/cli/src/commands/git/push.rs +++ b/cli/src/commands/git/push.rs @@ -26,7 +26,7 @@ use jj_lib::git::GitBranchPushTargets; use jj_lib::git::GitPushError; use jj_lib::object_id::ObjectId; use jj_lib::op_store::RefTarget; -use jj_lib::refs::classify_branch_push_action; +use jj_lib::refs::classify_bookmark_push_action; use jj_lib::refs::BranchPushAction; use jj_lib::refs::BranchPushUpdate; use jj_lib::refs::LocalAndRemoteRef; @@ -56,51 +56,52 @@ use crate::ui::Ui; /// Push to a Git remote /// -/// By default, pushes any branches pointing to -/// `remote_branches(remote=)..@`. 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. +/// By default, pushes any bookmarks pointing to +/// `remote_bookmarks(remote=)..@`. Use `--bookmark` to push specific +/// bookmarks. Use `--all` to push all bookmarks. Use `--change` to generate +/// bookmark names based on the change IDs of specific commits. /// -/// Before the command actually moves, creates, or deletes a remote branch, it +/// Before the command actually moves, creates, or deletes a remote bookmark, it /// makes several [safety checks]. If there is a problem, you may need to run -/// `jj git fetch --remote ` and/or resolve some [branch +/// `jj git fetch --remote ` and/or resolve some [bookmark /// conflicts]. /// /// [safety checks]: -/// https://martinvonz.github.io/jj/latest/branches/#pushing-branches-safety-checks +/// https://martinvonz.github.io/jj/latest/bookmarks/#pushing-bookmarks-safety-checks /// -/// [branch conflicts]: -/// https://martinvonz.github.io/jj/latest/branches/#conflicts +/// [bookmark conflicts]: +/// https://martinvonz.github.io/jj/latest/bookmarks/#conflicts #[derive(clap::Args, Clone, Debug)] -#[command(group(ArgGroup::new("specific").args(&["branch", "change", "revisions"]).multiple(true)))] +#[command(group(ArgGroup::new("specific").args(&["bookmark", "change", "revisions"]).multiple(true)))] #[command(group(ArgGroup::new("what").args(&["all", "deleted", "tracked"]).conflicts_with("specific")))] pub struct GitPushArgs { /// The remote to push to (only named remotes are supported) #[arg(long)] remote: Option, - /// Push only this branch, or branches matching a pattern (can be repeated) + /// Push only this bookmark, or bookmarks matching a pattern (can be + /// repeated) /// /// By default, the specified name matches exactly. Use `glob:` prefix to - /// select branches by wildcard pattern. For details, see + /// select bookmarks by wildcard pattern. For details, see /// https://martinvonz.github.io/jj/latest/revsets#string-patterns. #[arg(long, short, value_parser = StringPattern::parse)] - branch: Vec, - /// Push all branches (including deleted branches) + bookmark: Vec, + /// Push all bookmarks (including deleted bookmarks) #[arg(long)] all: bool, - /// Push all tracked branches (including deleted branches) + /// Push all tracked bookmarks (including deleted bookmarks) /// - /// This usually means that the branch was already pushed to or fetched from - /// the relevant remote. For details, see - /// https://martinvonz.github.io/jj/latest/branches#remotes-and-tracked-branches + /// This usually means that the bookmark was already pushed to or fetched + /// from the relevant remote. For details, see + /// https://martinvonz.github.io/jj/latest/bookmarks#remotes-and-tracked-bookmarks #[arg(long)] tracked: bool, - /// Push all deleted branches + /// Push all deleted bookmarks /// - /// Only tracked branches can be successfully deleted on the remote. A - /// warning will be printed if any untracked branches on the remote - /// correspond to missing local branches. + /// Only tracked bookmarks can be successfully deleted on the remote. A + /// warning will be printed if any untracked bookmarks on the remote + /// correspond to missing local bookmarks. #[arg(long)] deleted: bool, /// Allow pushing commits with empty descriptions @@ -109,10 +110,10 @@ pub struct GitPushArgs { /// Allow pushing commits that are private #[arg(long)] allow_private: bool, - /// Push branches pointing to these commits (can be repeated) + /// Push bookmarks pointing to these commits (can be repeated) #[arg(long, short)] revisions: Vec, - /// Push this commit by creating a branch based on its change ID (can be + /// Push this commit by creating a bookmark based on its change ID (can be /// repeated) #[arg(long, short)] change: Vec, @@ -121,10 +122,10 @@ pub struct GitPushArgs { dry_run: bool, } -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(", ")), +fn make_bookmark_term(bookmark_names: &[impl fmt::Display]) -> String { + match bookmark_names { + [bookmark_name] => format!("bookmark {}", bookmark_name), + bookmark_names => format!("bookmarks {}", bookmark_names.iter().join(", ")), } } @@ -154,87 +155,87 @@ pub fn cmd_git_push( let repo = workspace_command.repo().clone(); let mut tx = workspace_command.start_transaction(); let tx_description; - let mut branch_updates = vec![]; + let mut bookmark_updates = vec![]; if args.all { - for (branch_name, targets) in repo.view().local_remote_branches(&remote) { - match classify_branch_update(branch_name, &remote, targets) { - Ok(Some(update)) => branch_updates.push((branch_name.to_owned(), update)), + for (bookmark_name, targets) in repo.view().local_remote_bookmarks(&remote) { + match classify_bookmark_update(bookmark_name, &remote, targets) { + Ok(Some(update)) => bookmark_updates.push((bookmark_name.to_owned(), update)), Ok(None) => {} Err(reason) => reason.print(ui)?, } } - tx_description = format!("push all branches to git remote {remote}"); + tx_description = format!("push all bookmarks to git remote {remote}"); } else if args.tracked { - for (branch_name, targets) in repo.view().local_remote_branches(&remote) { + for (bookmark_name, targets) in repo.view().local_remote_bookmarks(&remote) { if !targets.remote_ref.is_tracking() { continue; } - match classify_branch_update(branch_name, &remote, targets) { - Ok(Some(update)) => branch_updates.push((branch_name.to_owned(), update)), + match classify_bookmark_update(bookmark_name, &remote, targets) { + Ok(Some(update)) => bookmark_updates.push((bookmark_name.to_owned(), update)), Ok(None) => {} Err(reason) => reason.print(ui)?, } } - tx_description = format!("push all tracked branches to git remote {remote}"); + tx_description = format!("push all tracked bookmarks to git remote {remote}"); } else if args.deleted { - for (branch_name, targets) in repo.view().local_remote_branches(&remote) { + for (bookmark_name, targets) in repo.view().local_remote_bookmarks(&remote) { if targets.local_target.is_present() { continue; } - match classify_branch_update(branch_name, &remote, targets) { - Ok(Some(update)) => branch_updates.push((branch_name.to_owned(), update)), + match classify_bookmark_update(bookmark_name, &remote, targets) { + Ok(Some(update)) => bookmark_updates.push((bookmark_name.to_owned(), update)), Ok(None) => {} Err(reason) => reason.print(ui)?, } } - tx_description = format!("push all deleted branches to git remote {remote}"); + tx_description = format!("push all deleted bookmarks to git remote {remote}"); } else { - let mut seen_branches: HashSet<&str> = HashSet::new(); + let mut seen_bookmarks: HashSet<&str> = HashSet::new(); - // Process --change branches first because matching branches can be moved. - let change_branch_names = update_change_branches( + // Process --change bookmarks first because matching bookmarks can be moved. + let change_bookmark_names = update_change_bookmarks( ui, &mut tx, &args.change, - &command.settings().push_branch_prefix(), + &command.settings().push_bookmark_prefix(), )?; - let change_branches = change_branch_names.iter().map(|branch_name| { + let change_bookmarks = change_bookmark_names.iter().map(|bookmark_name| { let targets = LocalAndRemoteRef { - local_target: tx.repo().view().get_local_branch(branch_name), - remote_ref: tx.repo().view().get_remote_branch(branch_name, &remote), + local_target: tx.repo().view().get_local_bookmark(bookmark_name), + remote_ref: tx.repo().view().get_remote_bookmark(bookmark_name, &remote), }; - (branch_name.as_ref(), targets) + (bookmark_name.as_ref(), targets) }); - let branches_by_name = find_branches_to_push(repo.view(), &args.branch, &remote)?; - for (branch_name, targets) in change_branches.chain(branches_by_name.iter().copied()) { - if !seen_branches.insert(branch_name) { + let bookmarks_by_name = find_bookmarks_to_push(repo.view(), &args.bookmark, &remote)?; + for (bookmark_name, targets) in change_bookmarks.chain(bookmarks_by_name.iter().copied()) { + if !seen_bookmarks.insert(bookmark_name) { continue; } - match classify_branch_update(branch_name, &remote, targets) { - Ok(Some(update)) => branch_updates.push((branch_name.to_owned(), update)), + match classify_bookmark_update(bookmark_name, &remote, targets) { + Ok(Some(update)) => bookmark_updates.push((bookmark_name.to_owned(), update)), Ok(None) => writeln!( ui.status(), - "Branch {branch_name}@{remote} already matches {branch_name}", + "Branch {bookmark_name}@{remote} already matches {bookmark_name}", )?, Err(reason) => return Err(reason.into()), } } let use_default_revset = - args.branch.is_empty() && args.change.is_empty() && args.revisions.is_empty(); - let branches_targeted = find_branches_targeted_by_revisions( + args.bookmark.is_empty() && args.change.is_empty() && args.revisions.is_empty(); + let bookmarks_targeted = find_bookmarks_targeted_by_revisions( ui, tx.base_workspace_helper(), &remote, &args.revisions, use_default_revset, )?; - for &(branch_name, targets) in &branches_targeted { - if !seen_branches.insert(branch_name) { + for &(bookmark_name, targets) in &bookmarks_targeted { + if !seen_bookmarks.insert(bookmark_name) { continue; } - match classify_branch_update(branch_name, &remote, targets) { - Ok(Some(update)) => branch_updates.push((branch_name.to_owned(), update)), + match classify_bookmark_update(bookmark_name, &remote, targets) { + Ok(Some(update)) => bookmark_updates.push((bookmark_name.to_owned(), update)), Ok(None) => {} Err(reason) => reason.print(ui)?, } @@ -242,22 +243,22 @@ pub fn cmd_git_push( tx_description = format!( "push {} to git remote {}", - make_branch_term( - &branch_updates + make_bookmark_term( + &bookmark_updates .iter() - .map(|(branch, _)| branch.as_str()) + .map(|(bookmark, _)| bookmark.as_str()) .collect_vec() ), &remote ); } - if branch_updates.is_empty() { + if bookmark_updates.is_empty() { writeln!(ui.status(), "Nothing changed.")?; return Ok(()); } - let mut branch_push_direction = HashMap::new(); - for (branch_name, update) in &branch_updates { + let mut bookmark_push_direction = HashMap::new(); + for (bookmark_name, update) in &bookmark_updates { let BranchPushUpdate { old_target: Some(old_target), new_target: Some(new_target), @@ -266,8 +267,8 @@ pub fn cmd_git_push( continue; }; assert_ne!(old_target, new_target); - branch_push_direction.insert( - branch_name.to_string(), + bookmark_push_direction.insert( + bookmark_name.to_string(), if repo.index().is_ancestor(old_target, new_target) { BranchMoveDirection::Forward } else if repo.index().is_ancestor(new_target, old_target) { @@ -278,28 +279,29 @@ pub fn cmd_git_push( ); } - validate_commits_ready_to_push(&branch_updates, &remote, &tx, command, args)?; + validate_commits_ready_to_push(&bookmark_updates, &remote, &tx, command, args)?; writeln!(ui.status(), "Branch changes to push to {}:", &remote)?; - for (branch_name, update) in &branch_updates { + for (bookmark_name, update) in &bookmark_updates { match (&update.old_target, &update.new_target) { (Some(old_target), Some(new_target)) => { let old = short_commit_hash(old_target); let new = short_commit_hash(new_target); - // TODO(ilyagr): Add color. Once there is color, "Move branch ... sideways" may - // read more naturally than "Move sideways branch ...". Without color, it's hard - // to see at a glance if one branch among many was moved sideways (say). - // TODO: People on Discord suggest "Move branch ... forward by n commits", - // possibly "Move branch ... sideways (X forward, Y back)". - let msg = match branch_push_direction.get(branch_name).unwrap() { + // TODO(ilyagr): Add color. Once there is color, "Move bookmark ... sideways" + // may read more naturally than "Move sideways bookmark ...". + // Without color, it's hard to see at a glance if one bookmark + // among many was moved sideways (say). TODO: People on Discord + // suggest "Move bookmark ... forward by n commits", + // possibly "Move bookmark ... sideways (X forward, Y back)". + let msg = match bookmark_push_direction.get(bookmark_name).unwrap() { BranchMoveDirection::Forward => { - format!("Move forward branch {branch_name} from {old} to {new}") + format!("Move forward bookmark {bookmark_name} from {old} to {new}") } BranchMoveDirection::Backward => { - format!("Move backward branch {branch_name} from {old} to {new}") + format!("Move backward bookmark {bookmark_name} from {old} to {new}") } BranchMoveDirection::Sideways => { - format!("Move sideways branch {branch_name} from {old} to {new}") + format!("Move sideways bookmark {bookmark_name} from {old} to {new}") } }; writeln!(ui.status(), " {msg}")?; @@ -307,19 +309,19 @@ pub fn cmd_git_push( (Some(old_target), None) => { writeln!( ui.status(), - " Delete branch {branch_name} from {}", + " Delete bookmark {bookmark_name} from {}", short_commit_hash(old_target) )?; } (None, Some(new_target)) => { writeln!( ui.status(), - " Add branch {branch_name} to {}", + " Add bookmark {bookmark_name} to {}", short_commit_hash(new_target) )?; } (None, None) => { - panic!("Not pushing any change to branch {branch_name}"); + panic!("Not pushing any change to bookmark {bookmark_name}"); } } } @@ -329,24 +331,24 @@ pub fn cmd_git_push( return Ok(()); } - let targets = GitBranchPushTargets { branch_updates }; + let targets = GitBranchPushTargets { bookmark_updates }; let mut writer = GitSidebandProgressMessageWriter::new(ui); let mut sideband_progress_callback = |progress_message: &[u8]| { _ = writer.write(ui, progress_message); }; with_remote_git_callbacks(ui, Some(&mut sideband_progress_callback), |cb| { - git::push_branches(tx.mut_repo(), &git_repo, &remote, &targets, cb) + git::push_bookmarks(tx.mut_repo(), &git_repo, &remote, &targets, cb) }) .map_err(|err| match err { GitPushError::InternalGitError(err) => map_git_error(err), GitPushError::RefInUnexpectedLocation(refs) => user_error_with_hint( format!( - "Refusing to push a branch that unexpectedly moved on the remote. Affected refs: \ - {}", + "Refusing to push a bookmark that unexpectedly moved on the remote. Affected \ + refs: {}", refs.join(", ") ), - "Try fetching from the remote, then make the branch point to where you want it to be, \ - and push again.", + "Try fetching from the remote, then make the bookmark point to where you want it to \ + be, and push again.", ), _ => user_error(err), })?; @@ -358,7 +360,7 @@ pub fn cmd_git_push( /// Validates that the commits that will be pushed are ready (have authorship /// information, are not conflicted, etc.) fn validate_commits_ready_to_push( - branch_updates: &[(String, BranchPushUpdate)], + bookmark_updates: &[(String, BranchPushUpdate)], remote: &str, tx: &WorkspaceCommandTransaction, command: &CommandHelper, @@ -367,13 +369,13 @@ fn validate_commits_ready_to_push( let workspace_helper = tx.base_workspace_helper(); let repo = workspace_helper.repo(); - let new_heads = branch_updates + let new_heads = bookmark_updates .iter() .filter_map(|(_, update)| update.new_target.clone()) .collect_vec(); let old_heads = repo .view() - .remote_branches(remote) + .remote_bookmarks(remote) .flat_map(|(_, old_head)| old_head.target.added_ids()) .cloned() .collect_vec(); @@ -476,40 +478,42 @@ impl From for CommandError { } } -fn classify_branch_update( - branch_name: &str, +fn classify_bookmark_update( + bookmark_name: &str, remote_name: &str, targets: LocalAndRemoteRef, ) -> Result, RejectedBranchUpdateReason> { - let push_action = classify_branch_push_action(targets); + let push_action = classify_bookmark_push_action(targets); match push_action { BranchPushAction::AlreadyMatches => Ok(None), BranchPushAction::LocalConflicted => Err(RejectedBranchUpdateReason { - message: format!("Branch {branch_name} is conflicted"), + message: format!("Branch {bookmark_name} is conflicted"), hint: Some( - "Run `jj branch list` to inspect, and use `jj branch set` to fix it up.".to_owned(), + "Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up." + .to_owned(), ), }), BranchPushAction::RemoteConflicted => Err(RejectedBranchUpdateReason { - message: format!("Branch {branch_name}@{remote_name} is conflicted"), - hint: Some("Run `jj git fetch` to update the conflicted remote branch.".to_owned()), + message: format!("Branch {bookmark_name}@{remote_name} is conflicted"), + hint: Some("Run `jj git fetch` to update the conflicted remote bookmark.".to_owned()), }), BranchPushAction::RemoteUntracked => Err(RejectedBranchUpdateReason { - message: format!("Non-tracking remote branch {branch_name}@{remote_name} exists"), + message: format!("Non-tracking remote bookmark {bookmark_name}@{remote_name} exists"), hint: Some(format!( - "Run `jj branch track {branch_name}@{remote_name}` to import the remote branch." + "Run `jj bookmark track {bookmark_name}@{remote_name}` to import the remote \ + bookmark." )), }), BranchPushAction::Update(update) => Ok(Some(update)), } } -/// Creates or moves branches based on the change IDs. -fn update_change_branches( +/// Creates or moves bookmarks based on the change IDs. +fn update_change_bookmarks( ui: &Ui, tx: &mut WorkspaceCommandTransaction, changes: &[RevisionArg], - branch_prefix: &str, + bookmark_prefix: &str, ) -> Result, CommandError> { if changes.is_empty() { // NOTE: we don't want resolve_some_revsets_default_single to fail if the @@ -517,71 +521,71 @@ fn update_change_branches( return Ok(vec![]); } - let mut branch_names = Vec::new(); + let mut bookmark_names = Vec::new(); let workspace_command = tx.base_workspace_helper(); let all_commits = workspace_command.resolve_some_revsets_default_single(changes)?; for commit in all_commits { let workspace_command = tx.base_workspace_helper(); let short_change_id = short_change_hash(commit.change_id()); - let mut branch_name = format!("{branch_prefix}{}", commit.change_id().hex()); + let mut bookmark_name = format!("{bookmark_prefix}{}", commit.change_id().hex()); let view = tx.base_repo().view(); - if view.get_local_branch(&branch_name).is_absent() { - // A local branch with the full change ID doesn't exist already, so use the + if view.get_local_bookmark(&bookmark_name).is_absent() { + // A local bookmark with the full change ID doesn't exist already, so use the // short ID if it's not ambiguous (which it shouldn't be most of the time). if workspace_command .resolve_single_rev(&RevisionArg::from(short_change_id.clone())) .is_ok() { - // Short change ID is not ambiguous, so update the branch name to use it. - branch_name = format!("{branch_prefix}{short_change_id}"); + // Short change ID is not ambiguous, so update the bookmark name to use it. + bookmark_name = format!("{bookmark_prefix}{short_change_id}"); }; } - if view.get_local_branch(&branch_name).is_absent() { + if view.get_local_bookmark(&bookmark_name).is_absent() { writeln!( ui.status(), - "Creating branch {branch_name} for revision {short_change_id}", + "Creating bookmark {bookmark_name} for revision {short_change_id}", )?; } tx.mut_repo() - .set_local_branch_target(&branch_name, RefTarget::normal(commit.id().clone())); - branch_names.push(branch_name); + .set_local_bookmark_target(&bookmark_name, RefTarget::normal(commit.id().clone())); + bookmark_names.push(bookmark_name); } - Ok(branch_names) + Ok(bookmark_names) } -fn find_branches_to_push<'a>( +fn find_bookmarks_to_push<'a>( view: &'a View, - branch_patterns: &[StringPattern], + bookmark_patterns: &[StringPattern], remote_name: &str, ) -> Result)>, CommandError> { - let mut matching_branches = vec![]; + let mut matching_bookmarks = vec![]; let mut unmatched_patterns = vec![]; - for pattern in branch_patterns { + for pattern in bookmark_patterns { let mut matches = view - .local_remote_branches_matching(pattern, remote_name) + .local_remote_bookmarks_matching(pattern, remote_name) .filter(|(_, targets)| { // If the remote exists but is not tracking, the absent local shouldn't - // be considered a deleted branch. + // be considered a deleted bookmark. targets.local_target.is_present() || targets.remote_ref.is_tracking() }) .peekable(); if matches.peek().is_none() { unmatched_patterns.push(pattern); } - matching_branches.extend(matches); + matching_bookmarks.extend(matches); } match &unmatched_patterns[..] { - [] => Ok(matching_branches), - [pattern] if pattern.is_exact() => Err(user_error(format!("No such branch: {pattern}"))), + [] => Ok(matching_bookmarks), + [pattern] if pattern.is_exact() => Err(user_error(format!("No such bookmark: {pattern}"))), patterns => Err(user_error(format!( - "No matching branches for patterns: {}", + "No matching bookmarks for patterns: {}", patterns.iter().join(", ") ))), } } -fn find_branches_targeted_by_revisions<'a>( +fn find_bookmarks_targeted_by_revisions<'a>( ui: &Ui, workspace_command: &'a WorkspaceCommandHelper, remote_name: &str, @@ -593,44 +597,44 @@ fn find_branches_targeted_by_revisions<'a>( let Some(wc_commit_id) = workspace_command.get_wc_commit_id().cloned() else { return Err(user_error("Nothing checked out in this workspace")); }; - let current_branches_expression = RevsetExpression::remote_branches( + let current_bookmarks_expression = RevsetExpression::remote_bookmarks( StringPattern::everything(), StringPattern::exact(remote_name), None, ) .range(&RevsetExpression::commit(wc_commit_id)) - .intersection(&RevsetExpression::branches(StringPattern::everything())); - let current_branches_revset = - current_branches_expression.evaluate_programmatic(workspace_command.repo().as_ref())?; - revision_commit_ids.extend(current_branches_revset.iter()); + .intersection(&RevsetExpression::bookmarks(StringPattern::everything())); + let current_bookmarks_revset = current_bookmarks_expression + .evaluate_programmatic(workspace_command.repo().as_ref())?; + revision_commit_ids.extend(current_bookmarks_revset.iter()); if revision_commit_ids.is_empty() { writeln!( ui.warning_default(), - "No branches found in the default push revset: \ - remote_branches(remote={remote_name})..@" + "No bookmarks found in the default push revset: \ + remote_bookmarks(remote={remote_name})..@" )?; } } for rev_arg in revisions { let mut expression = workspace_command.parse_revset(rev_arg)?; - expression.intersect_with(&RevsetExpression::branches(StringPattern::everything())); + expression.intersect_with(&RevsetExpression::bookmarks(StringPattern::everything())); let mut commit_ids = expression.evaluate_to_commit_ids()?.peekable(); if commit_ids.peek().is_none() { writeln!( ui.warning_default(), - "No branches point to the specified revisions: {rev_arg}" + "No bookmarks point to the specified revisions: {rev_arg}" )?; } revision_commit_ids.extend(commit_ids); } - let branches_targeted = workspace_command + let bookmarks_targeted = workspace_command .repo() .view() - .local_remote_branches(remote_name) + .local_remote_bookmarks(remote_name) .filter(|(_, targets)| { let mut local_ids = targets.local_target.added_ids(); local_ids.any(|id| revision_commit_ids.contains(id)) }) .collect_vec(); - Ok(branches_targeted) + Ok(bookmarks_targeted) } diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 615003e07c..d2ceab663e 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -16,7 +16,7 @@ mod abandon; mod backout; #[cfg(feature = "bench")] mod bench; -mod branch; +mod bookmark; mod checkout; mod commit; mod config; @@ -76,9 +76,11 @@ enum Command { #[cfg(feature = "bench")] #[command(subcommand)] Bench(bench::BenchCommand), + // TODO: Remove in jj 0.27+ + #[command(subcommand, hide = true)] + Branch(bookmark::BookmarkCommand), #[command(subcommand)] - Branch(branch::BranchCommand), - // TODO: Delete `cat` in jj 0.25+ + Bookmark(bookmark::BookmarkCommand), #[command(alias = "print", hide = true)] Cat(file::show::FileShowArgs), #[command(hide = true)] @@ -108,7 +110,7 @@ enum Command { Init(init::InitArgs), Interdiff(interdiff::InterdiffArgs), Log(log::LogArgs), - /// Merge work from multiple branches (DEPRECATED, use `jj new`) + /// Merge work from multiple bookmarks (DEPRECATED, use `jj new`) /// /// Unlike most other VCSs, `jj merge` does not implicitly include the /// working copy revision's parent as one of the parents of the merge; @@ -181,7 +183,8 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co Command::Backout(args) => backout::cmd_backout(ui, command_helper, args), #[cfg(feature = "bench")] Command::Bench(args) => bench::cmd_bench(ui, command_helper, args), - Command::Branch(args) => branch::cmd_branch(ui, command_helper, args), + Command::Branch(args) => bookmark::cmd_branch(ui, command_helper, args), + Command::Bookmark(args) => bookmark::cmd_bookmark(ui, command_helper, args), Command::Cat(args) => { let cmd = renamed_cmd("cat", "file show", file::show::cmd_file_show); cmd(ui, command_helper, args) @@ -242,7 +245,7 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co } /// Wraps deprecated command of `old_name` which has been renamed to `new_name`. -fn renamed_cmd( +pub(crate) fn renamed_cmd( old_name: &'static str, new_name: &'static str, cmd: impl Fn(&mut Ui, &CommandHelper, &Args) -> Result<(), CommandError>, diff --git a/cli/src/commands/new.rs b/cli/src/commands/new.rs index 3408ebf386..12e719ba21 100644 --- a/cli/src/commands/new.rs +++ b/cli/src/commands/new.rs @@ -172,7 +172,7 @@ pub(crate) fn cmd_new( if should_advance_branches { advance_branches_target = Some(parent_commit_ids[0].clone()); advanceable_branches = - workspace_command.get_advanceable_branches(parent_commits[0].parent_ids())?; + workspace_command.get_advanceable_bookmarks(parent_commits[0].parent_ids())?; } }; workspace_command.check_rewritable(children_commits.iter().ids())?; @@ -222,7 +222,7 @@ pub(crate) fn cmd_new( // Does nothing if there's no branches to advance. if let Some(target) = advance_branches_target { - tx.advance_branches(advanceable_branches, &target); + tx.advance_bookmarks(advanceable_branches, &target); } tx.finish(ui, "new empty commit")?; diff --git a/cli/src/commands/operation/diff.rs b/cli/src/commands/operation/diff.rs index 8ddf556ab0..dd2ea583c1 100644 --- a/cli/src/commands/operation/diff.rs +++ b/cli/src/commands/operation/diff.rs @@ -300,15 +300,15 @@ pub fn show_op_diff( } } - let changed_local_branches = diff_named_ref_targets( - from_repo.view().local_branches(), - to_repo.view().local_branches(), + let changed_local_bookmarks = diff_named_ref_targets( + from_repo.view().local_bookmarks(), + to_repo.view().local_bookmarks(), ) .collect_vec(); - if !changed_local_branches.is_empty() { + if !changed_local_bookmarks.is_empty() { writeln!(formatter)?; writeln!(formatter, "Changed local branches:")?; - for (name, (from_target, to_target)) in changed_local_branches { + for (name, (from_target, to_target)) in changed_local_bookmarks { writeln!(formatter, "{}:", name)?; write_ref_target_summary( formatter, @@ -357,8 +357,8 @@ pub fn show_op_diff( } let changed_remote_branches = diff_named_remote_refs( - from_repo.view().all_remote_branches(), - to_repo.view().all_remote_branches(), + from_repo.view().all_remote_bookmarks(), + to_repo.view().all_remote_bookmarks(), ) // Skip updates to the local git repo, since they should typically be covered in // local branches. diff --git a/cli/src/commands/operation/mod.rs b/cli/src/commands/operation/mod.rs index 5c72fea51b..3e8ad9ab9f 100644 --- a/cli/src/commands/operation/mod.rs +++ b/cli/src/commands/operation/mod.rs @@ -96,7 +96,7 @@ fn view_with_desired_portions_restored( }; jj_lib::op_store::View { head_ids: repo_source.head_ids.clone(), - local_branches: repo_source.local_branches.clone(), + local_bookmarks: repo_source.local_bookmarks.clone(), tags: repo_source.tags.clone(), remote_views: remote_source.remote_views.clone(), git_refs: current_view.git_refs.clone(), diff --git a/cli/src/commands/rebase.rs b/cli/src/commands/rebase.rs index 8b7699d344..417d054038 100644 --- a/cli/src/commands/rebase.rs +++ b/cli/src/commands/rebase.rs @@ -50,7 +50,7 @@ use crate::ui::Ui; /// Move revisions to different parent(s) /// /// There are three different ways of specifying which revisions to rebase: -/// `-b` to rebase a whole branch, `-s` to rebase a revision and its +/// `-b` to rebase a whole bookmark, `-s` to rebase a revision and its /// descendants, and `-r` to rebase a single commit. If none of them is /// specified, it defaults to `-b @`. /// @@ -73,8 +73,8 @@ use crate::ui::Ui; /// J J /// ``` /// -/// With `-b`, the command rebases the whole "branch" containing the specified -/// revision. A "branch" is the set of commits that includes: +/// With `-b`, the command rebases the whole "bookmark" containing the specified +/// revision. A "bookmark" is the set of commits that includes: /// /// * the specified revision and ancestors that are not also ancestors of the /// destination @@ -84,7 +84,7 @@ use crate::ui::Ui; /// `(Y..X)::` (which is equivalent to `jj rebase -s 'roots(Y..X)' -d Y` for a /// single root). For example, either `jj rebase -b L -d O` or `jj rebase -b M /// -d O` would transform your history like this (because `L` and `M` are on the -/// same "branch", relative to the destination): +/// same "bookmark", relative to the destination): /// /// ```text /// O N' @@ -134,10 +134,10 @@ use crate::ui::Ui; /// commit. This is true in general; it is not specific to this command. #[derive(clap::Args, Clone, Debug)] #[command(verbatim_doc_comment)] -#[command(group(ArgGroup::new("to_rebase").args(&["branch", "source", "revisions"])))] +#[command(group(ArgGroup::new("to_rebase").args(&["bookmark", "source", "revisions"])))] #[command(group(ArgGroup::new("target").args(&["destination", "insert_after", "insert_before"]).multiple(true).required(true)))] pub(crate) struct RebaseArgs { - /// Rebase the whole branch relative to destination's ancestors (can be + /// Rebase the whole bookmark relative to destination's ancestors (can be /// repeated) /// /// `jj rebase -b=br -d=dst` is equivalent to `jj rebase '-s=roots(dst..br)' @@ -145,7 +145,7 @@ pub(crate) struct RebaseArgs { /// /// If none of `-b`, `-s`, or `-r` is provided, then the default is `-b @`. #[arg(long, short)] - branch: Vec, + bookmark: Vec, /// Rebase specified revision(s) together with their trees of descendants /// (can be repeated) @@ -180,7 +180,7 @@ pub(crate) struct RebaseArgs { visible_alias = "after", conflicts_with = "destination", conflicts_with = "source", - conflicts_with = "branch" + conflicts_with = "bookmark" )] insert_after: Vec, /// The revision(s) to insert before (can be repeated to create a merge @@ -193,7 +193,7 @@ pub(crate) struct RebaseArgs { visible_alias = "before", conflicts_with = "destination", conflicts_with = "source", - conflicts_with = "branch" + conflicts_with = "bookmark" )] insert_before: Vec, @@ -314,41 +314,41 @@ pub(crate) fn cmd_rebase( .resolve_some_revsets_default_single(&args.destination)? .into_iter() .collect_vec(); - let branch_commits = if args.branch.is_empty() { + let bookmark_commits = if args.bookmark.is_empty() { IndexSet::from([workspace_command.resolve_single_rev(&RevisionArg::AT)?]) } else { - workspace_command.resolve_some_revsets_default_single(&args.branch)? + workspace_command.resolve_some_revsets_default_single(&args.bookmark)? }; - rebase_branch( + rebase_bookmark( ui, command.settings(), &mut workspace_command, new_parents, - &branch_commits, + &bookmark_commits, rebase_options, )?; } Ok(()) } -fn rebase_branch( +fn rebase_bookmark( ui: &mut Ui, settings: &UserSettings, workspace_command: &mut WorkspaceCommandHelper, new_parents: Vec, - branch_commits: &IndexSet, + bookmark_commits: &IndexSet, rebase_options: RebaseOptions, ) -> Result<(), CommandError> { let parent_ids = new_parents .iter() .map(|commit| commit.id().clone()) .collect_vec(); - let branch_commit_ids = branch_commits + let bookmark_commit_ids = bookmark_commits .iter() .map(|commit| commit.id().clone()) .collect_vec(); let roots_expression = RevsetExpression::commits(parent_ids) - .range(&RevsetExpression::commits(branch_commit_ids)) + .range(&RevsetExpression::commits(bookmark_commit_ids)) .roots(); let root_commits: IndexSet<_> = roots_expression .evaluate_programmatic(workspace_command.repo().as_ref()) diff --git a/cli/src/commands/status.rs b/cli/src/commands/status.rs index c367474a85..4067fe3149 100644 --- a/cli/src/commands/status.rs +++ b/cli/src/commands/status.rs @@ -34,7 +34,7 @@ use crate::ui::Ui; /// * The working copy commit and its (first) parent, and a summary of the /// changes between them /// -/// * Conflicted branches (see https://github.com/martinvonz/jj/blob/main/docs/branches.md) +/// * Conflicted bookmarks (see https://github.com/martinvonz/jj/blob/main/docs/bookmarks.md) #[derive(clap::Args, Clone, Debug)] #[command(visible_alias = "st")] pub(crate) struct StatusArgs { @@ -143,47 +143,50 @@ pub(crate) fn cmd_status( writeln!(formatter, "No working copy")?; } - let conflicted_local_branches = repo + let conflicted_local_bookmarks = repo .view() - .local_branches() + .local_bookmarks() .filter(|(_, target)| target.has_conflict()) - .map(|(branch_name, _)| branch_name) + .map(|(bookmark_name, _)| bookmark_name) .collect_vec(); - let conflicted_remote_branches = repo + let conflicted_remote_bookmarks = repo .view() - .all_remote_branches() + .all_remote_bookmarks() .filter(|(_, remote_ref)| remote_ref.target.has_conflict()) .map(|(full_name, _)| full_name) .collect_vec(); - if !conflicted_local_branches.is_empty() { + if !conflicted_local_bookmarks.is_empty() { writeln!( formatter.labeled("conflict"), - "These branches have conflicts:" + "These bookmarks have conflicts:" )?; - for branch_name in conflicted_local_branches { + for bookmark_name in conflicted_local_bookmarks { write!(formatter, " ")?; - write!(formatter.labeled("branch"), "{branch_name}")?; + write!(formatter.labeled("bookmark"), "{bookmark_name}")?; writeln!(formatter)?; } writeln!( formatter, - " Use `jj branch list` to see details. Use `jj branch set -r ` to \ + " Use `jj bookmark list` to see details. Use `jj bookmark set -r ` to \ resolve." )?; } - if !conflicted_remote_branches.is_empty() { + if !conflicted_remote_bookmarks.is_empty() { writeln!( formatter.labeled("conflict"), - "These remote branches have conflicts:" + "These remote bookmarks have conflicts:" )?; - for (branch_name, remote_name) in conflicted_remote_branches { + for (bookmark_name, remote_name) in conflicted_remote_bookmarks { write!(formatter, " ")?; - write!(formatter.labeled("branch"), "{branch_name}@{remote_name}")?; + write!( + formatter.labeled("bookmark"), + "{bookmark_name}@{remote_name}" + )?; writeln!(formatter)?; } writeln!( formatter, - " Use `jj branch list` to see details. Use `jj git fetch` to resolve." + " Use `jj bookmark list` to see details. Use `jj git fetch` to resolve." )?; } diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index a1b54a557e..2639426fd5 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -450,16 +450,16 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> { #[derive(Default)] pub struct CommitKeywordCache<'repo> { // Build index lazily, and Rc to get away from &self lifetime. - branches_index: OnceCell>, + bookmarks_index: OnceCell>, tags_index: OnceCell>, git_refs_index: OnceCell>, is_immutable_fn: OnceCell>>, } impl<'repo> CommitKeywordCache<'repo> { - pub fn branches_index(&self, repo: &dyn Repo) -> &Rc { - self.branches_index - .get_or_init(|| Rc::new(build_branches_index(repo))) + pub fn bookmarks_index(&self, repo: &dyn Repo) -> &Rc { + self.bookmarks_index + .get_or_init(|| Rc::new(build_bookmarks_index(repo))) } pub fn tags_index(&self, repo: &dyn Repo) -> &Rc { @@ -569,10 +569,13 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm }, ); map.insert( - "branches", + "bookmarks", |language, _build_ctx, self_property, function| { function.expect_no_arguments()?; - let index = language.keyword_cache.branches_index(language.repo).clone(); + let index = language + .keyword_cache + .bookmarks_index(language.repo) + .clone(); let out_property = self_property.map(move |commit| { index .get(commit.id()) @@ -585,10 +588,13 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm }, ); map.insert( - "local_branches", + "local_bookmarks", |language, _build_ctx, self_property, function| { function.expect_no_arguments()?; - let index = language.keyword_cache.branches_index(language.repo).clone(); + let index = language + .keyword_cache + .bookmarks_index(language.repo) + .clone(); let out_property = self_property.map(move |commit| { index .get(commit.id()) @@ -601,10 +607,13 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm }, ); map.insert( - "remote_branches", + "remote_bookmarks", |language, _build_ctx, self_property, function| { function.expect_no_arguments()?; - let index = language.keyword_cache.branches_index(language.repo).clone(); + let index = language + .keyword_cache + .bookmarks_index(language.repo) + .clone(); let out_property = self_property.map(move |commit| { index .get(commit.id()) @@ -1115,14 +1124,14 @@ impl RefNamesIndex { } } -fn build_branches_index(repo: &dyn Repo) -> RefNamesIndex { +fn build_bookmarks_index(repo: &dyn Repo) -> RefNamesIndex { let mut index = RefNamesIndex::default(); - for (branch_name, branch_target) in repo.view().branches() { - let local_target = branch_target.local_target; - let remote_refs = branch_target.remote_refs; + for (bookmark_name, bookmark_target) in repo.view().bookmarks() { + let local_target = bookmark_target.local_target; + let remote_refs = bookmark_target.remote_refs; if local_target.is_present() { let ref_name = RefName::local( - branch_name, + bookmark_name, local_target.clone(), remote_refs.iter().map(|&(_, remote_ref)| remote_ref), ); @@ -1130,7 +1139,7 @@ fn build_branches_index(repo: &dyn Repo) -> RefNamesIndex { } for &(remote_name, remote_ref) in &remote_refs { let ref_name = - RefName::remote(branch_name, remote_name, remote_ref.clone(), local_target); + RefName::remote(bookmark_name, remote_name, remote_ref.clone(), local_target); index.insert(remote_ref.target.added_ids(), ref_name); } } diff --git a/cli/src/git_util.rs b/cli/src/git_util.rs index 0b46b1d66f..4bb55717d0 100644 --- a/cli/src/git_util.rs +++ b/cli/src/git_util.rs @@ -319,10 +319,14 @@ impl RefStatus { repo: &dyn Repo, ) -> Self { let (ref_name, ref_kind, tracking_status) = match ref_name { - RefName::RemoteBranch { branch, remote } => ( - format!("{branch}@{remote}"), + RefName::RemoteBranch { bookmark, remote } => ( + format!("{bookmark}@{remote}"), RefKind::Branch, - if repo.view().get_remote_branch(branch, remote).is_tracking() { + if repo + .view() + .get_remote_bookmark(bookmark, remote) + .is_tracking() + { TrackingStatus::Tracked } else { TrackingStatus::Untracked diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index a9eba717f5..bb323b7ed7 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -13,16 +13,16 @@ This document contains the help content for the `jj` command-line program. * [`jj`↴](#jj) * [`jj abandon`↴](#jj-abandon) * [`jj backout`↴](#jj-backout) -* [`jj branch`↴](#jj-branch) -* [`jj branch create`↴](#jj-branch-create) -* [`jj branch delete`↴](#jj-branch-delete) -* [`jj branch forget`↴](#jj-branch-forget) -* [`jj branch list`↴](#jj-branch-list) -* [`jj branch move`↴](#jj-branch-move) -* [`jj branch rename`↴](#jj-branch-rename) -* [`jj branch set`↴](#jj-branch-set) -* [`jj branch track`↴](#jj-branch-track) -* [`jj branch untrack`↴](#jj-branch-untrack) +* [`jj bookmark`↴](#jj-bookmark) +* [`jj bookmark create`↴](#jj-bookmark-create) +* [`jj bookmark delete`↴](#jj-bookmark-delete) +* [`jj bookmark forget`↴](#jj-bookmark-forget) +* [`jj bookmark list`↴](#jj-bookmark-list) +* [`jj bookmark move`↴](#jj-bookmark-move) +* [`jj bookmark rename`↴](#jj-bookmark-rename) +* [`jj bookmark set`↴](#jj-bookmark-set) +* [`jj bookmark track`↴](#jj-bookmark-track) +* [`jj bookmark untrack`↴](#jj-bookmark-untrack) * [`jj commit`↴](#jj-commit) * [`jj config`↴](#jj-config) * [`jj config edit`↴](#jj-config-edit) @@ -112,7 +112,7 @@ To get started, see the tutorial at https://github.com/martinvonz/jj/blob/main/d * `abandon` — Abandon a revision * `backout` — Apply the reverse of a revision on top of another revision -* `branch` — Manage branches +* `bookmark` — Manage bookmarks * `commit` — Update the description and create a new change on top * `config` — Manage config options * `describe` — Update the change description or other metadata @@ -225,99 +225,99 @@ Apply the reverse of a revision on top of another revision -## `jj branch` +## `jj bookmark` -Manage branches +Manage bookmarks -For information about branches, see https://github.com/martinvonz/jj/blob/main/docs/branches.md. +For information about bookmarks, see https://github.com/martinvonz/jj/blob/main/docs/bookmarks.md. -**Usage:** `jj branch ` +**Usage:** `jj bookmark ` ###### **Subcommands:** -* `create` — Create a new branch -* `delete` — Delete an existing branch and propagate the deletion to remotes on the next push -* `forget` — Forget everything about a branch, including its local and remote targets -* `list` — List branches and their targets -* `move` — Move existing branches to target revision -* `rename` — Rename `old` branch name to `new` branch name -* `set` — Create or update a branch to point to a certain commit -* `track` — Start tracking given remote branches -* `untrack` — Stop tracking given remote branches +* `create` — Create a new bookmark +* `delete` — Delete an existing bookmark and propagate the deletion to remotes on the next push +* `forget` — Forget everything about a bookmark, including its local and remote targets +* `list` — List bookmarks and their targets +* `move` — Move existing bookmarks to target revision +* `rename` — Rename `old` bookmark name to `new` bookmark name +* `set` — Create or update a bookmark to point to a certain commit +* `track` — Start tracking given remote bookmarks +* `untrack` — Stop tracking given remote bookmarks -## `jj branch create` +## `jj bookmark create` -Create a new branch +Create a new bookmark -**Usage:** `jj branch create [OPTIONS] ...` +**Usage:** `jj bookmark create [OPTIONS] ...` ###### **Arguments:** -* `` — The branches to create +* `` — The bookmarks to create ###### **Options:** -* `-r`, `--revision ` — The branch's target revision +* `-r`, `--revision ` — The bookmark's target revision -## `jj branch delete` +## `jj bookmark delete` -Delete an existing branch and propagate the deletion to remotes on the next push +Delete an existing bookmark and propagate the deletion to remotes on the next push -**Usage:** `jj branch delete ...` +**Usage:** `jj bookmark delete ...` ###### **Arguments:** -* `` — The branches to delete +* `` — The bookmarks 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. + By default, the specified name matches exactly. Use `glob:` prefix to select bookmarks by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. -## `jj branch forget` +## `jj bookmark forget` -Forget everything about a branch, including its local and remote targets +Forget everything about a bookmark, 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. +A forgotten bookmark will not impact remotes on future pushes. It will be recreated on future pulls if it still exists in the remote. -**Usage:** `jj branch forget ...` +**Usage:** `jj bookmark forget ...` ###### **Arguments:** -* `` — The branches to forget +* `` — The bookmarks 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. + By default, the specified name matches exactly. Use `glob:` prefix to select bookmarks by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. -## `jj branch list` +## `jj bookmark list` -List branches and their targets +List bookmarks 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 "+". +By default, a tracking remote bookmark will be included only if its target is different from the local target. A non-tracking remote bookmark won't be listed. For a conflicted bookmark (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. +For information about bookmarks, see https://github.com/martinvonz/jj/blob/main/docs/bookmarks.md. -**Usage:** `jj branch list [OPTIONS] [NAMES]...` +**Usage:** `jj bookmark list [OPTIONS] [NAMES]...` ###### **Arguments:** -* `` — Show branches whose local name matches +* `` — Show bookmarks 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. + By default, the specified name matches exactly. Use `glob:` prefix to select bookmarks by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns. ###### **Options:** -* `-a`, `--all-remotes` — Show all tracking and non-tracking remote branches including the ones whose targets are synchronized with the local branches -* `-t`, `--tracked` — Show remote tracked branches only. Omits local Git-tracking branches by default -* `-c`, `--conflicted` — Show conflicted branches only -* `-r`, `--revisions ` — Show branches whose local targets are in the given revisions +* `-a`, `--all-remotes` — Show all tracking and non-tracking remote bookmarks including the ones whose targets are synchronized with the local bookmarks +* `-t`, `--tracked` — Show remote tracked bookmarks only. Omits local Git-tracking bookmarks by default +* `-c`, `--conflicted` — Show conflicted bookmarks only +* `-r`, `--revisions ` — Show bookmarks 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. -* `-T`, `--template