// 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 clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::RefTarget; use jj_lib::ref_name::RefNameBuf; use super::is_fast_forward; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; use crate::cli_util::has_tracked_remote_bookmarks; use crate::command_error::CommandError; use crate::command_error::user_error_with_hint; use crate::complete; use crate::revset_util; use crate::ui::Ui; /// Create or update a bookmark to point to a certain commit #[derive(clap::Args, Clone, Debug)] pub struct BookmarkSetArgs { /// The bookmark's target revision #[arg( long, short, default_value = "@", visible_alias = "to", value_name = "REVSET", add = ArgValueCompleter::new(complete::revset_expression_all), )] revision: RevisionArg, /// Allow moving the bookmark backwards or sideways #[arg(long, short = 'B')] allow_backwards: bool, /// The bookmarks to update #[arg( required = true, value_parser = revset_util::parse_bookmark_name, add = ArgValueCandidates::new(complete::local_bookmarks), )] names: Vec, } pub fn cmd_bookmark_set( ui: &mut Ui, command: &CommandHelper, args: &BookmarkSetArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let target_commit = workspace_command.resolve_single_rev(ui, &args.revision)?; let repo = workspace_command.repo().as_ref(); 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_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 bookmark backwards or sideways: {name}", name = name.as_symbol() ), "Use --allow-backwards to allow it.", )); } } if target_commit.is_discardable(repo)? { writeln!(ui.warning_default(), "Target revision is empty.")?; } let mut tx = workspace_command.start_transaction(); 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_bookmark_count > 0 { write!( formatter, "Created {new_bookmark_count} bookmarks pointing to " )?; tx.write_commit_summary(formatter.as_mut(), &target_commit)?; writeln!(formatter)?; } if moved_bookmark_count > 0 { write!(formatter, "Moved {moved_bookmark_count} bookmarks to ")?; tx.write_commit_summary(formatter.as_mut(), &target_commit)?; writeln!(formatter)?; } } tx.finish( ui, format!( "point bookmark {names} to commit {id}", names = bookmark_names.iter().map(|n| n.as_symbol()).join(", "), id = target_commit.id().hex() ), )?; Ok(()) }