Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

everything: Rename branches to bookmarks #4341

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
183 changes: 93 additions & 90 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
PhilipMetzger marked this conversation as resolved.
Show resolved Hide resolved
/// 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<StringPattern>,
disabled_branches: Vec<StringPattern>,
struct AdvanceBookmarksSettings {
enabled_bookmarks: Vec<StringPattern>,
disabled_bookmarks: Vec<StringPattern>,
}

impl AdvanceBranchesSettings {
impl AdvanceBookmarksSettings {
fn from_config(config: &config::Config) -> Result<Self, CommandError> {
let get_setting = |setting_key| {
let setting = format!("experimental-advance-branches.{setting_key}");
Expand All @@ -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()
PhilipMetzger marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -1781,42 +1783,43 @@ 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<Item = &'a CommitId>,
) -> Result<Vec<AdvanceableBranch>, CommandError> {
let ab_settings = AdvanceBranchesSettings::from_config(self.settings().config())?;
) -> Result<Vec<AdvanceableBookmark>, 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(),
});
}
}
}

Ok(advanceable_branches)
Ok(advanceable_bookmarks)
}
}

Expand Down Expand Up @@ -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<AdvanceableBranch>, 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<AdvanceableBookmark>, 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()),
);
}
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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<Self, Self::Err> {
// 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
Expand All @@ -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}")
}
}

Expand Down
Loading