// 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 advance;mod create;mod delete;mod forget;mod list;mod r#move;mod rename;mod set;mod track;mod untrack; use std::io; use itertools::Itertools as _;use jj_lib::backend::CommitId;use jj_lib::iter_util::fallible_any;use jj_lib::op_store::RefTarget;use jj_lib::op_store::RemoteRef;use jj_lib::ref_name::RefName;use jj_lib::ref_name::RemoteName;use jj_lib::ref_name::RemoteRefSymbol;use jj_lib::ref_name::RemoteRefSymbolBuf;use jj_lib::repo::Repo;use jj_lib::str_util::StringExpression;use jj_lib::str_util::StringMatcher;use jj_lib::view::View; use self::advance::BookmarkAdvanceArgs;use self::advance::cmd_bookmark_advance;use self::create::BookmarkCreateArgs;use self::create::cmd_bookmark_create;use self::delete::BookmarkDeleteArgs;use self::delete::cmd_bookmark_delete;use self::forget::BookmarkForgetArgs;use self::forget::cmd_bookmark_forget;use self::list::BookmarkListArgs;use self::list::cmd_bookmark_list;use self::r#move::BookmarkMoveArgs;use self::r#move::cmd_bookmark_move;use self::rename::BookmarkRenameArgs;use self::rename::cmd_bookmark_rename;use self::set::BookmarkSetArgs;use self::set::cmd_bookmark_set;use self::track::BookmarkTrackArgs;use self::track::cmd_bookmark_track;use self::untrack::BookmarkUntrackArgs;use self::untrack::cmd_bookmark_untrack;use crate::cli_util::CommandHelper;use crate::command_error::CommandError;use crate::ui::Ui; // Unlike most other aliases, `b` is defined in the config and can be overridden// by the user. /// Manage bookmarks [default alias: b]////// See [`jj help -k bookmarks`] for more information.////// [`jj help -k bookmarks`]:/// https://docs.jj-vcs.dev/latest/bookmarks#[derive(clap::Subcommand, Clone, Debug)]pub enum BookmarkCommand { #[command(visible_alias("a"))] Advance(BookmarkAdvanceArgs), #[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 async fn cmd_bookmark( ui: &mut Ui, command: &CommandHelper, subcommand: &BookmarkCommand,) -> Result<(), CommandError> { match subcommand { BookmarkCommand::Advance(args) => cmd_bookmark_advance(ui, command, args).await, BookmarkCommand::Create(args) => cmd_bookmark_create(ui, command, args).await, BookmarkCommand::Delete(args) => cmd_bookmark_delete(ui, command, args).await, BookmarkCommand::Forget(args) => cmd_bookmark_forget(ui, command, args).await, BookmarkCommand::List(args) => cmd_bookmark_list(ui, command, args).await, BookmarkCommand::Move(args) => cmd_bookmark_move(ui, command, args).await, BookmarkCommand::Rename(args) => cmd_bookmark_rename(ui, command, args).await, BookmarkCommand::Set(args) => cmd_bookmark_set(ui, command, args).await, BookmarkCommand::Track(args) => cmd_bookmark_track(ui, command, args).await, BookmarkCommand::Untrack(args) => cmd_bookmark_untrack(ui, command, args).await, }} fn resolve_trackable_remote_bookmarks<'a>( ui: &Ui, view: &'a View, symbols: &'a [RemoteRefSymbolBuf],) -> Result<Vec<(RemoteRefSymbol<'a>, &'a RemoteRef)>, CommandError> { let mut trackable_refs = vec![]; let mut unmatched_symbols = vec![]; for symbol in symbols { let symbol = symbol.as_ref(); let remote_ref = view.get_remote_bookmark(symbol); if remote_ref.is_present() || view.get_local_bookmark(symbol.name).is_present() && view.get_remote_view(symbol.remote).is_some() { trackable_refs.push((symbol, remote_ref)); } else { unmatched_symbols.push(symbol); } } trackable_refs.sort_unstable_by_key(|(sym, _)| *sym); trackable_refs.dedup_by(|(sym1, _), (sym2, _)| sym1 == sym2); if !unmatched_symbols.is_empty() { writeln!( ui.warning_default(), "No matching remote bookmarks for names: {}", unmatched_symbols.iter().join(", ") )?; } Ok(trackable_refs)} fn trackable_remote_bookmarks_matching<'a>( view: &'a View, bookmark_matcher: &StringMatcher, remote_matcher: &StringMatcher,) -> impl Iterator<Item = (RemoteRefSymbol<'a>, &'a RemoteRef)> { let present_or_tracked_matches = view.remote_bookmarks_matching(bookmark_matcher, remote_matcher); let absent_matches = view.remote_views_matching(remote_matcher) .flat_map(move |(remote, remote_view)| { view.local_bookmarks_matching(bookmark_matcher) .filter(|&(name, _)| !remote_view.bookmarks.contains_key(name)) .map(|(name, _)| (name.to_remote_symbol(remote), RemoteRef::absent_ref())) }); itertools::chain(present_or_tracked_matches, absent_matches)} async fn is_fast_forward( repo: &dyn Repo, old_target: &RefTarget, new_target_id: &CommitId,) -> Result<bool, CommandError> { 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. let found = fallible_any(old_target.added_ids(), async |old| { repo.index().is_ancestor(old, new_target_id).await }) .await?; Ok(found) } else { Ok(true) }} /// Warns about exact patterns that don't match local bookmarks.fn warn_unmatched_local_bookmarks( ui: &Ui, view: &View, name_expr: &StringExpression,) -> io::Result<()> { let mut names = name_expr .exact_strings() .map(RefName::new) .filter(|name| view.get_local_bookmark(name).is_absent()) .peekable(); if names.peek().is_none() { return Ok(()); } writeln!( ui.warning_default(), "No matching bookmarks for names: {}", names.map(|name| name.as_symbol()).join(", ") )} /// Warns about exact patterns that don't match local or remote bookmarks.fn warn_unmatched_local_or_remote_bookmarks( ui: &Ui, view: &View, name_expr: &StringExpression,) -> io::Result<()> { let mut names = name_expr .exact_strings() .map(RefName::new) .filter(|&name| { view.get_local_bookmark(name).is_absent() && view .remote_views() .all(|(_, remote_view)| !remote_view.bookmarks.contains_key(name)) }) .peekable(); if names.peek().is_none() { return Ok(()); } writeln!( ui.warning_default(), "No matching bookmarks for names: {}", names.map(|name| name.as_symbol()).join(", ") )} /// Warns about exact patterns that don't match remotes.fn warn_unmatched_remotes(ui: &Ui, view: &View, name_expr: &StringExpression) -> io::Result<()> { let mut names = name_expr .exact_strings() .map(RemoteName::new) .filter(|name| view.get_remote_view(name).is_none()) .peekable(); if names.peek().is_none() { return Ok(()); } writeln!( ui.warning_default(), "No matching remotes for names: {}", names.map(|name| name.as_symbol()).join(", ") )}
// 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 advance;
mod create;
mod delete;
mod forget;
mod list;
mod r#move;
mod rename;
mod set;
mod track;
mod untrack;
use std::io;
use itertools::Itertools as _;
use jj_lib::backend::CommitId;
use jj_lib::iter_util::fallible_any;
use jj_lib::op_store::RefTarget;
use jj_lib::op_store::RemoteRef;
use jj_lib::ref_name::RefName;
use jj_lib::ref_name::RemoteName;
use jj_lib::ref_name::RemoteRefSymbol;
use jj_lib::ref_name::RemoteRefSymbolBuf;
use jj_lib::repo::Repo;
use jj_lib::str_util::StringExpression;
use jj_lib::str_util::StringMatcher;
use jj_lib::view::View;
use self::advance::BookmarkAdvanceArgs;
use self::advance::cmd_bookmark_advance;
use self::create::BookmarkCreateArgs;
use self::create::cmd_bookmark_create;
use self::delete::BookmarkDeleteArgs;
use self::delete::cmd_bookmark_delete;
use self::forget::BookmarkForgetArgs;
use self::forget::cmd_bookmark_forget;
use self::list::BookmarkListArgs;
use self::list::cmd_bookmark_list;
use self::r#move::BookmarkMoveArgs;
use self::r#move::cmd_bookmark_move;
use self::rename::BookmarkRenameArgs;
use self::rename::cmd_bookmark_rename;
use self::set::BookmarkSetArgs;
use self::set::cmd_bookmark_set;
use self::track::BookmarkTrackArgs;
use self::track::cmd_bookmark_track;
use self::untrack::BookmarkUntrackArgs;
use self::untrack::cmd_bookmark_untrack;
use crate::cli_util::CommandHelper;
use crate::command_error::CommandError;
use crate::ui::Ui;
// Unlike most other aliases, `b` is defined in the config and can be overridden
// by the user.
/// Manage bookmarks [default alias: b]
///
/// See [`jj help -k bookmarks`] for more information.
///
/// [`jj help -k bookmarks`]:
/// https://docs.jj-vcs.dev/latest/bookmarks
#[derive(clap::Subcommand, Clone, Debug)]
pub enum BookmarkCommand {
#[command(visible_alias("a"))]
Advance(BookmarkAdvanceArgs),
#[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 async fn cmd_bookmark(
ui: &mut Ui,
command: &CommandHelper,
subcommand: &BookmarkCommand,
) -> Result<(), CommandError> {
match subcommand {
BookmarkCommand::Advance(args) => cmd_bookmark_advance(ui, command, args).await,
BookmarkCommand::Create(args) => cmd_bookmark_create(ui, command, args).await,
BookmarkCommand::Delete(args) => cmd_bookmark_delete(ui, command, args).await,
BookmarkCommand::Forget(args) => cmd_bookmark_forget(ui, command, args).await,
BookmarkCommand::List(args) => cmd_bookmark_list(ui, command, args).await,
BookmarkCommand::Move(args) => cmd_bookmark_move(ui, command, args).await,
BookmarkCommand::Rename(args) => cmd_bookmark_rename(ui, command, args).await,
BookmarkCommand::Set(args) => cmd_bookmark_set(ui, command, args).await,
BookmarkCommand::Track(args) => cmd_bookmark_track(ui, command, args).await,
BookmarkCommand::Untrack(args) => cmd_bookmark_untrack(ui, command, args).await,
}
}
fn resolve_trackable_remote_bookmarks<'a>(
ui: &Ui,
view: &'a View,
symbols: &'a [RemoteRefSymbolBuf],
) -> Result<Vec<(RemoteRefSymbol<'a>, &'a RemoteRef)>, CommandError> {
let mut trackable_refs = vec![];
let mut unmatched_symbols = vec![];
for symbol in symbols {
let symbol = symbol.as_ref();
let remote_ref = view.get_remote_bookmark(symbol);
if remote_ref.is_present()
|| view.get_local_bookmark(symbol.name).is_present()
&& view.get_remote_view(symbol.remote).is_some()
{
trackable_refs.push((symbol, remote_ref));
} else {
unmatched_symbols.push(symbol);
}
}
trackable_refs.sort_unstable_by_key(|(sym, _)| *sym);
trackable_refs.dedup_by(|(sym1, _), (sym2, _)| sym1 == sym2);
if !unmatched_symbols.is_empty() {
writeln!(
ui.warning_default(),
"No matching remote bookmarks for names: {}",
unmatched_symbols.iter().join(", ")
)?;
}
Ok(trackable_refs)
}
fn trackable_remote_bookmarks_matching<'a>(
view: &'a View,
bookmark_matcher: &StringMatcher,
remote_matcher: &StringMatcher,
) -> impl Iterator<Item = (RemoteRefSymbol<'a>, &'a RemoteRef)> {
let present_or_tracked_matches =
view.remote_bookmarks_matching(bookmark_matcher, remote_matcher);
let absent_matches =
view.remote_views_matching(remote_matcher)
.flat_map(move |(remote, remote_view)| {
view.local_bookmarks_matching(bookmark_matcher)
.filter(|&(name, _)| !remote_view.bookmarks.contains_key(name))
.map(|(name, _)| (name.to_remote_symbol(remote), RemoteRef::absent_ref()))
});
itertools::chain(present_or_tracked_matches, absent_matches)
}
async fn is_fast_forward(
repo: &dyn Repo,
old_target: &RefTarget,
new_target_id: &CommitId,
) -> Result<bool, CommandError> {
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.
let found = fallible_any(old_target.added_ids(), async |old| {
repo.index().is_ancestor(old, new_target_id).await
})
.await?;
Ok(found)
} else {
Ok(true)
}
}
/// Warns about exact patterns that don't match local bookmarks.
fn warn_unmatched_local_bookmarks(
ui: &Ui,
view: &View,
name_expr: &StringExpression,
) -> io::Result<()> {
let mut names = name_expr
.exact_strings()
.map(RefName::new)
.filter(|name| view.get_local_bookmark(name).is_absent())
.peekable();
if names.peek().is_none() {
return Ok(());
}
writeln!(
ui.warning_default(),
"No matching bookmarks for names: {}",
names.map(|name| name.as_symbol()).join(", ")
)
}
/// Warns about exact patterns that don't match local or remote bookmarks.
fn warn_unmatched_local_or_remote_bookmarks(
ui: &Ui,
view: &View,
name_expr: &StringExpression,
) -> io::Result<()> {
let mut names = name_expr
.exact_strings()
.map(RefName::new)
.filter(|&name| {
view.get_local_bookmark(name).is_absent()
&& view
.remote_views()
.all(|(_, remote_view)| !remote_view.bookmarks.contains_key(name))
})
.peekable();
if names.peek().is_none() {
return Ok(());
}
writeln!(
ui.warning_default(),
"No matching bookmarks for names: {}",
names.map(|name| name.as_symbol()).join(", ")
)
}
/// Warns about exact patterns that don't match remotes.
fn warn_unmatched_remotes(ui: &Ui, view: &View, name_expr: &StringExpression) -> io::Result<()> {
let mut names = name_expr
.exact_strings()
.map(RemoteName::new)
.filter(|name| view.get_remote_view(name).is_none())
.peekable();
if names.peek().is_none() {
return Ok(());
}
writeln!(
ui.warning_default(),
"No matching remotes for names: {}",
names.map(|name| name.as_symbol()).join(", ")
)
}