From d9c68e08b1a575e63443cacb130806536de06e8a Mon Sep 17 00:00:00 2001 From: Philip Metzger Date: Wed, 21 Aug 2024 21:59:15 +0200 Subject: [PATCH] everything: Rename `branches` to `bookmarks` Jujutsu's branches do not behave like Git branches, which is a major hurdle for people adopting it from Git. They rather behave like Mercurial's (hg) bookmarks. We've had multiple discussions about it in the last ~1.5 years about this rename in the Discord, where multiple people agreed that this _false_ familiarity does not help anyone. Initially we were reluctant to do it but overtime, more and more users agreed that `bookmark` was a better for name the current mechanism. This may be hard break for current `jj branch` users, but it will immensly help Jujutsu's future, by defining it as our first own term. The `[experimental-moving-branches]` config option is currently left alone, to force not another large config update for users, since the last time this happened was when `jj log -T show` was removed, which immediately resulted in breaking users and introduced soft deprecations. This name change will also make it easier to introduce Topics (#3402) as _topological branches_ with a easier model. This was mostly done via LSP, ripgrep and sed and a whole bunch of manual changes either from me being lazy or thankfully pointed out by reviewers. --- CHANGELOG.md | 11 + cli/src/cli_util.rs | 183 ++--- .../commands/{branch => bookmark}/create.rs | 52 +- .../commands/{branch => bookmark}/delete.rs | 32 +- .../commands/{branch => bookmark}/forget.rs | 40 +- cli/src/commands/{branch => bookmark}/list.rs | 99 +-- cli/src/commands/bookmark/mod.rs | 198 +++++ cli/src/commands/{branch => bookmark}/move.rs | 69 +- cli/src/commands/bookmark/rename.rs | 93 +++ cli/src/commands/{branch => bookmark}/set.rs | 64 +- .../commands/{branch => bookmark}/track.rs | 62 +- .../commands/{branch => bookmark}/untrack.rs | 39 +- cli/src/commands/branch/mod.rs | 194 ----- cli/src/commands/branch/rename.rs | 88 --- cli/src/commands/commit.rs | 4 +- cli/src/commands/git/clone.rs | 4 +- cli/src/commands/git/init.rs | 4 +- cli/src/commands/git/push.rs | 270 +++---- cli/src/commands/mod.rs | 18 +- cli/src/commands/new.rs | 4 +- cli/src/commands/operation/diff.rs | 14 +- cli/src/commands/operation/mod.rs | 2 +- cli/src/commands/status.rs | 36 +- cli/src/commit_templater.rs | 46 +- cli/src/config/colors.toml | 5 + cli/src/config/templates.toml | 18 +- cli/src/git_util.rs | 6 +- cli/tests/cli-reference@.md.snap | 214 +++--- cli/tests/runner.rs | 4 +- cli/tests/test_abandon_command.rs | 6 +- cli/tests/test_advance_bookmarks.rs | 455 +++++++++++ cli/tests/test_advance_branches.rs | 448 ----------- cli/tests/test_alias.rs | 14 +- cli/tests/test_backout_command.rs | 2 +- ...ch_command.rs => test_bookmark_command.rs} | 704 +++++++++--------- cli/tests/test_builtin_aliases.rs | 8 +- cli/tests/test_commit_template.rs | 123 +-- cli/tests/test_diffedit_command.rs | 2 +- cli/tests/test_duplicate_command.rs | 2 +- cli/tests/test_file_chmod_command.rs | 4 +- cli/tests/test_fix_command.rs | 56 +- cli/tests/test_git_clone.rs | 28 +- cli/tests/test_git_colocated.rs | 119 +-- cli/tests/test_git_fetch.rs | 213 +++--- cli/tests/test_git_import_export.rs | 68 +- cli/tests/test_git_init.rs | 64 +- cli/tests/test_git_private_commits.rs | 56 +- cli/tests/test_git_push.rs | 695 +++++++++-------- cli/tests/test_git_remotes.rs | 10 +- cli/tests/test_gitignores.rs | 2 +- cli/tests/test_global_opts.rs | 6 +- cli/tests/test_immutable_commits.rs | 24 +- cli/tests/test_init_command.rs | 32 +- cli/tests/test_interdiff_command.rs | 12 +- cli/tests/test_log_command.rs | 144 ++-- cli/tests/test_move_command.rs | 22 +- cli/tests/test_new_command.rs | 20 +- cli/tests/test_next_prev_commands.rs | 14 +- cli/tests/test_operations.rs | 512 +++++++------ cli/tests/test_rebase_command.rs | 26 +- cli/tests/test_resolve_command.rs | 4 +- cli/tests/test_restore_command.rs | 4 +- cli/tests/test_revset_output.rs | 98 +-- cli/tests/test_split_command.rs | 24 +- cli/tests/test_squash_command.rs | 68 +- cli/tests/test_status_command.rs | 4 +- cli/tests/test_tag_command.rs | 16 +- cli/tests/test_undo.rs | 80 +- cli/tests/test_unsquash_command.rs | 18 +- docs/FAQ.md | 32 +- docs/bookmarks.md | 221 ++++++ docs/branches.md | 217 ------ docs/config.md | 44 +- docs/contributing.md | 24 +- docs/git-comparison.md | 80 +- docs/git-compatibility.md | 2 +- docs/github.md | 98 +-- docs/glossary.md | 48 +- docs/templates.md | 12 +- lib/src/dag_walk.rs | 14 +- lib/src/git.rs | 30 +- lib/src/op_store.rs | 143 ++-- lib/src/refs.rs | 40 +- lib/src/repo.rs | 85 +-- lib/src/revset.rs | 254 ++++--- lib/src/settings.rs | 10 +- lib/src/simple_op_store.rs | 160 ++-- lib/src/view.rs | 140 ++-- lib/tests/test_git.rs | 614 +++++++-------- lib/tests/test_index.rs | 2 +- lib/tests/test_mut_repo.rs | 34 +- lib/tests/test_operations.rs | 4 +- lib/tests/test_revset.rs | 248 +++--- lib/tests/test_rewrite.rs | 87 ++- lib/tests/test_view.rs | 122 +-- mkdocs.yml | 2 +- 96 files changed, 4574 insertions(+), 4273 deletions(-) rename cli/src/commands/{branch => bookmark}/create.rs (62%) rename cli/src/commands/{branch => bookmark}/delete.rs (67%) rename cli/src/commands/{branch => bookmark}/forget.rs (62%) rename cli/src/commands/{branch => bookmark}/list.rs (65%) create mode 100644 cli/src/commands/bookmark/mod.rs rename cli/src/commands/{branch => bookmark}/move.rs (63%) create mode 100644 cli/src/commands/bookmark/rename.rs rename cli/src/commands/{branch => bookmark}/set.rs (61%) rename cli/src/commands/{branch => bookmark}/track.rs (62%) rename cli/src/commands/{branch => bookmark}/untrack.rs (64%) delete mode 100644 cli/src/commands/branch/mod.rs delete mode 100644 cli/src/commands/branch/rename.rs create mode 100644 cli/tests/test_advance_bookmarks.rs delete mode 100644 cli/tests/test_advance_branches.rs rename cli/tests/{test_branch_command.rs => test_bookmark_command.rs} (67%) create mode 100644 docs/bookmarks.md delete mode 100644 docs/branches.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fb2f58e0b..61c69b60c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,22 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * Invalid `ui.graph.style` configuration is now an error. +* The builtin template `branch_list` has been renamed to `bookmark_list` in + lieu of the `jj branch` deprecation. + ### Deprecations * `jj obslog` is now called `jj evolution-log`/`jj evolog`. `jj obslog` remains as an alias. +* `jj branch` has been deprecated in favor of `jj bookmark`. + + **Rationale:** Jujutsu's branches don't behave like Git branches, which a + confused many newcomers, as they expected a similar behavior given the name. + We've renamed them to "bookmarks" to match the actual behavior, as we think + that describes them better, and they also behave similar to Mercurial's + bookmarks. + ### New features * The new config option `snapshot.auto-track` lets you automatically track only diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index eabb519154..3d49b51d59 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -512,33 +512,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}"); @@ -558,28 +558,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" /// pattern. fn feature_enabled(&self) -> bool { - !self.enabled_branches.is_empty() + !self.enabled_bookmarks.is_empty() } } @@ -855,10 +857,10 @@ impl WorkspaceCommandHelper { } /// Imports branches and tags from the underlying Git repo, abandons old - /// branches. + /// 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 branch 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. @@ -1479,8 +1481,8 @@ See https://martinvonz.github.io/jj/latest/working-copy/#stale-working-copy \ } if self.working_copy_shared_with_git { - let failed_branches = git::export_refs(mut_repo)?; - print_failed_git_export(ui, &failed_branches)?; + let refs = git::export_refs(mut_repo)?; + print_failed_git_export(ui, &refs)?; } self.user_repo = ReadonlyUserRepo::new(tx.commit("snapshot working copy")); @@ -1596,8 +1598,8 @@ See https://martinvonz.github.io/jj/latest/working-copy/#stale-working-copy \ if let Some(wc_commit) = &maybe_new_wc_commit { git::reset_head(tx.repo_mut(), &git_repo, wc_commit)?; } - let failed_branches = git::export_refs(tx.repo_mut())?; - print_failed_git_export(ui, &failed_branches)?; + let refs = git::export_refs(tx.repo_mut())?; + print_failed_git_export(ui, &refs)?; } self.user_repo = ReadonlyUserRepo::new(tx.commit(description)); @@ -1781,34 +1783,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(), }); @@ -1816,7 +1819,7 @@ Then run `jj squash` to move the resolution into the conflicted commit."#, } } - Ok(advanceable_branches) + Ok(advanceable_bookmarks) } } @@ -1928,18 +1931,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.repo_mut().merge_local_branch( - &branch.name, - &RefTarget::normal(branch.old_commit_id), + self.repo_mut().merge_local_bookmark( + &bookmark.name, + &RefTarget::normal(bookmark.old_commit_id), &RefTarget::normal(move_to.clone()), ); } @@ -2233,35 +2236,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(()) @@ -2509,30 +2512,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 @@ -2545,27 +2548,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 b2924e78d0..a7d60fbc2d 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.repo_mut() - .set_local_branch_target(branch_name, RefTarget::normal(target_commit.id().clone())); + for bookmark_name in bookmark_names { + tx.repo_mut().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 d91a43bbe1..3a16801f10 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 - /// https://martinvonz.github.io/jj/latest/revsets/#string-patterns. + /// select bookmarks by wildcard pattern. For details, see + /// https://martinvonz.github.io/jj/latest/revsets/#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.repo_mut() - .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 62% rename from cli/src/commands/branch/forget.rs rename to cli/src/commands/bookmark/forget.rs index 5a33235937..0c2ca46e15 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 - /// https://martinvonz.github.io/jj/latest/revsets/#string-patterns. + /// select bookmarks by wildcard pattern. For details, see + /// https://martinvonz.github.io/jj/latest/revsets/#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.repo_mut() - .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.repo_mut() - .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 65% rename from cli/src/commands/branch/list.rs rename to cli/src/commands/bookmark/list.rs index d4ef3d4e29..6c230b80b6 100644 --- a/cli/src/commands/branch/list.rs +++ b/cli/src/commands/bookmark/list.rs @@ -25,71 +25,71 @@ 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://martinvonz.github.io/jj/latest/branches/. +/// For information about bookmarks, see +/// https://martinvonz.github.io/jj/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 - /// https://martinvonz.github.io/jj/latest/revsets/#string-patterns. + /// select bookmarks by wildcard pattern. For details, see + /// https://martinvonz.github.io/jj/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. /// - /// For the syntax, see https://martinvonz.github.io/jj/latest/templates/ + /// For the syntax, see https://martinvonz.github.io/jj/latest/docs/templates.md #[arg(long, short = 'T')] 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..689793f740 --- /dev/null +++ b/cli/src/commands/bookmark/mod.rs @@ -0,0 +1,198 @@ +// 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::ui::Ui; + +/// Manage bookmarks +/// +/// For information about bookmarks, see +/// https://martinvonz.github.io/jj/latest/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), + } +} + +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 f6db1ab8c8..bcc7d85f01 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://martinvonz.github.io/jj/latest/revsets/#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.repo_mut() - .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..f5b2a525a1 --- /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.repo_mut() + .set_local_bookmark_target(new_bookmark, ref_target); + tx.repo_mut() + .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 481760cec3..b87ea81e34 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.repo_mut() - .set_local_branch_target(branch_name, RefTarget::normal(target_commit.id().clone())); + for bookmark_name in bookmark_names { + tx.repo_mut().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 6988f2159b..93fed92363 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://martinvonz.github.io/jj/latest/revsets/#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.repo_mut() - .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 64% rename from cli/src/commands/branch/untrack.rs rename to cli/src/commands/bookmark/untrack.rs index a07b74a735..4ca79f812b 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://martinvonz.github.io/jj/latest/revsets/#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.repo_mut() - .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 365ede6141..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://martinvonz.github.io/jj/latest/branches/. -#[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 b814763620..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.repo_mut() - .set_local_branch_target(new_branch, ref_target); - tx.repo_mut() - .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 e8161fea4d..804e458407 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(); @@ -132,7 +132,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.repo_mut().edit(workspace_id, &new_wc_commit).unwrap(); diff --git a/cli/src/commands/git/clone.rs b/cli/src/commands/git/clone.rs index ee52e35d42..8c01432615 100644 --- a/cli/src/commands/git/clone.rs +++ b/cli/src/commands/git/clone.rs @@ -172,13 +172,13 @@ pub fn cmd_git_clone( let default_branch_remote_ref = workspace_command .repo() .view() - .get_remote_branch(default_branch, remote_name); + .get_remote_bookmark(default_branch, 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 .repo_mut() - .track_remote_branch(default_branch, remote_name); + .track_remote_bookmark(default_branch, 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 4b6a6d79af..328b4072a8 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::cli_util::WorkspaceCommandHelper; @@ -184,7 +184,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)?; diff --git a/cli/src/commands/git/push.rs b/cli/src/commands/git/push.rs index 2919614bf0..dd7179cb9e 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(", ")), } } @@ -156,85 +157,85 @@ pub fn cmd_git_push( let tx_description; let mut branch_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)) => branch_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)) => branch_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)) => branch_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)) => branch_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)) => branch_updates.push((bookmark_name.to_owned(), update)), Ok(None) => {} Err(reason) => reason.print(ui)?, } @@ -242,10 +243,10 @@ pub fn cmd_git_push( tx_description = format!( "push {} to git remote {}", - make_branch_term( + make_bookmark_term( &branch_updates .iter() - .map(|(branch, _)| branch.as_str()) + .map(|(bookmark, _)| bookmark.as_str()) .collect_vec() ), &remote @@ -256,8 +257,8 @@ pub fn cmd_git_push( 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 &branch_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) { @@ -281,25 +282,26 @@ pub fn cmd_git_push( validate_commits_ready_to_push(&branch_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 &branch_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}"); } } } @@ -341,12 +343,12 @@ pub fn cmd_git_push( 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.repo_mut() - .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 1a046e33d2..52c03c1743 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; @@ -77,8 +77,10 @@ enum Command { #[command(subcommand)] Bench(bench::BenchCommand), #[command(subcommand)] - Branch(branch::BranchCommand), - // TODO: Delete `cat` in jj 0.25+ + Bookmark(bookmark::BookmarkCommand), + // TODO: Remove in jj 0.28+ + #[command(subcommand, hide = true)] + Branch(bookmark::BookmarkCommand), #[command(alias = "print", hide = true)] Cat(file::show::FileShowArgs), #[command(hide = true)] @@ -110,7 +112,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; @@ -182,7 +184,11 @@ 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::Bookmark(args) => bookmark::cmd_bookmark(ui, command_helper, args), + Command::Branch(args) => { + let cmd = renamed_cmd("branch", "bookmark", bookmark::cmd_bookmark); + cmd(ui, command_helper, args) + } Command::Cat(args) => { let cmd = renamed_cmd("cat", "file show", file::show::cmd_file_show); cmd(ui, command_helper, args) @@ -243,7 +249,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 a068f4c891..7c087e3a11 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 1c309e7f66..a181f67a34 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 6c0fc204d7..aecb1199c9 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/status.rs b/cli/src/commands/status.rs index 4a24c9aad4..a7c3ac47c6 100644 --- a/cli/src/commands/status.rs +++ b/cli/src/commands/status.rs @@ -33,8 +33,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://martinvonz.github.io/jj/latest/branches/) +/// * Conflicted bookmarks (see https://martinvonz.github.io/jj/latest/bookmarks/) #[derive(clap::Args, Clone, Debug)] #[command(visible_alias = "st")] pub(crate) struct StatusArgs { @@ -143,47 +142,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 2ce8849e24..0de27daf88 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()) @@ -616,6 +625,11 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm Ok(L::wrap_ref_name_list(out_property)) }, ); + // TODO: Remove the following block after jj 0.28+ + map.insert("branches", map["bookmarks"]); + map.insert("local_branches", map["local_bookmarks"]); + map.insert("remote_branches", map["remote_bookmarks"]); + map.insert("tags", |language, _build_ctx, self_property, function| { function.expect_no_arguments()?; let index = language.keyword_cache.tags_index(language.repo).clone(); @@ -1115,14 +1129,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 +1144,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/config/colors.toml b/cli/src/config/colors.toml index 2eb1e12317..2c7a453347 100644 --- a/cli/src/config/colors.toml +++ b/cli/src/config/colors.toml @@ -25,6 +25,11 @@ "username" = "yellow" "timestamp" = "cyan" "working_copies" = "green" +"bookmark" = "magenta" +"bookmarks" = "magenta" +"local_bookmarks" = "magenta" +"remote_bookmarks" = "magenta" +# TODO: Remove in jj 0.28+ "branch" = "magenta" "branches" = "magenta" "local_branches" = "magenta" diff --git a/cli/src/config/templates.toml b/cli/src/config/templates.toml index ecc199a0ca..6fd6b7c268 100644 --- a/cli/src/config/templates.toml +++ b/cli/src/config/templates.toml @@ -1,18 +1,18 @@ [templates] -branch_list = ''' +bookmark_list = ''' if(remote, if(tracked, " " ++ separate(" ", - label("branch", "@" ++ remote), + label("bookmark", "@" ++ remote), format_tracked_remote_ref_distances(self), ) ++ format_ref_targets(self), - label("branch", name ++ "@" ++ remote) ++ format_ref_targets(self), + label("bookmark", name ++ "@" ++ remote) ++ format_ref_targets(self), ), - label("branch", name) ++ if(present, format_ref_targets(self), " (deleted)"), + label("bookmark", name) ++ if(present, format_ref_targets(self), " (deleted)"), ) ++ "\n" ''' -commit_summary = 'format_commit_summary_with_refs(self, branches)' +commit_summary = 'format_commit_summary_with_refs(self, bookmarks)' config_list = ''' if(overridden, @@ -51,7 +51,7 @@ if(root, format_short_change_id_with_hidden_and_divergent_info(self), if(author.email(), author.username(), email_placeholder), format_timestamp(committer.timestamp()), - branches, + bookmarks, tags, working_copies, git_head, @@ -76,7 +76,7 @@ if(root, format_short_change_id_with_hidden_and_divergent_info(self), format_short_signature(author), format_timestamp(committer.timestamp()), - branches, + bookmarks, tags, working_copies, git_head, @@ -99,7 +99,7 @@ builtin_log_detailed = ''' concat( "Commit ID: " ++ commit_id ++ "\n", "Change ID: " ++ change_id ++ "\n", - surround("Branches: ", "\n", separate(" ", local_branches, remote_branches)), + surround("Bookmarks: ", "\n", separate(" ", local_bookmarks, remote_bookmarks)), surround("Tags: ", "\n", tags), "Author: " ++ format_detailed_signature(author) ++ "\n", "Committer: " ++ format_detailed_signature(committer) ++ "\n", @@ -163,7 +163,7 @@ separate(" ", format_short_change_id(root.change_id()), label("root", "root()"), format_short_commit_id(root.commit_id()), - root.branches() + root.bookmarks() ) ++ "\n" ''' diff --git a/cli/src/git_util.rs b/cli/src/git_util.rs index 005466a5f0..8e2e803e8d 100644 --- a/cli/src/git_util.rs +++ b/cli/src/git_util.rs @@ -322,7 +322,11 @@ impl RefStatus { RefName::RemoteBranch { branch, remote } => ( format!("{branch}@{remote}"), RefKind::Branch, - if repo.view().get_remote_branch(branch, remote).is_tracking() { + if repo + .view() + .get_remote_bookmark(branch, 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 1b80105048..4bcf14a7df 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) @@ -113,7 +113,7 @@ To get started, see the tutorial at https://martinvonz.github.io/jj/latest/tutor * `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 @@ -226,201 +226,201 @@ 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://martinvonz.github.io/jj/latest/branches/. +For information about bookmarks, see https://martinvonz.github.io/jj/latest/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://martinvonz.github.io/jj/latest/revsets/#string-patterns. + By default, the specified name matches exactly. Use `glob:` prefix to select bookmarks by wildcard pattern. For details, see https://martinvonz.github.io/jj/latest/revsets/#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://martinvonz.github.io/jj/latest/revsets/#string-patterns. + By default, the specified name matches exactly. Use `glob:` prefix to select bookmarks by wildcard pattern. For details, see https://martinvonz.github.io/jj/latest/revsets/#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://martinvonz.github.io/jj/latest/branches/. +For information about bookmarks, see https://martinvonz.github.io/jj/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://martinvonz.github.io/jj/latest/revsets/#string-patterns. + By default, the specified name matches exactly. Use `glob:` prefix to select bookmarks by wildcard pattern. For details, see https://martinvonz.github.io/jj/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