Skip to main content

jj_cli/commands/bookmark/
untrack.rs

1// Copyright 2020-2023 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use clap_complete::ArgValueCandidates;
16use itertools::Itertools as _;
17use jj_lib::repo::Repo as _;
18use jj_lib::str_util::StringExpression;
19
20use super::find_trackable_remote_bookmarks;
21use super::trackable_remote_bookmarks_matching;
22use super::warn_unmatched_local_or_remote_bookmarks;
23use super::warn_unmatched_remotes;
24use crate::cli_util::CommandHelper;
25use crate::cli_util::RemoteBookmarkNamePattern;
26use crate::cli_util::default_ignored_remote_name;
27use crate::command_error::CommandError;
28use crate::command_error::cli_error;
29use crate::complete;
30use crate::revset_util::parse_union_name_patterns;
31use crate::ui::Ui;
32
33/// Stop tracking given remote bookmarks
34///
35/// An untracked remote bookmark is just a pointer to the last-fetched remote
36/// bookmark. It won't be imported as a local bookmark on future pulls.
37///
38/// If you want to forget a local bookmark while also untracking the
39/// corresponding remote bookmarks, use `jj bookmark forget` instead.
40#[derive(clap::Args, Clone, Debug)]
41pub struct BookmarkUntrackArgs {
42    /// Bookmark names to untrack
43    ///
44    /// By default, the specified pattern matches bookmark names with glob
45    /// syntax. You can also use other [string pattern syntax].
46    ///
47    /// [string pattern syntax]:
48    ///     https://docs.jj-vcs.dev/latest/revsets/#string-patterns
49    #[arg(required = true, value_name = "BOOKMARK")]
50    #[arg(add = ArgValueCandidates::new(complete::tracked_bookmarks))]
51    names: Vec<String>,
52
53    /// Remote names to untrack
54    ///
55    /// By default, the specified pattern matches remote names with glob syntax.
56    /// You can also use other [string pattern syntax].
57    ///
58    /// If no remote names are given, all remote bookmarks matching the bookmark
59    /// names will be untracked.
60    ///
61    /// [string pattern syntax]:
62    ///     https://docs.jj-vcs.dev/latest/revsets/#string-patterns
63    #[arg(long = "remote", value_name = "REMOTE")]
64    // TODO: Make this skip untracked remotes
65    #[arg(add = ArgValueCandidates::new(complete::git_remotes))]
66    remotes: Option<Vec<String>>,
67}
68
69pub async fn cmd_bookmark_untrack(
70    ui: &mut Ui,
71    command: &CommandHelper,
72    args: &BookmarkUntrackArgs,
73) -> Result<(), CommandError> {
74    let mut workspace_command = command.workspace_helper(ui)?;
75    let repo = workspace_command.repo().clone();
76    let view = repo.view();
77    let ignored_remote = default_ignored_remote_name(repo.store())
78        // suppress unmatched remotes warning for default-ignored remote
79        .filter(|name| view.get_remote_view(name).is_some());
80    let matched_refs = if args.remotes.is_none() && args.names.iter().all(|s| s.contains('@')) {
81        // TODO: Delete in jj 0.43+
82        writeln!(
83            ui.warning_default(),
84            "<bookmark>@<remote> syntax is deprecated, use `<bookmark> --remote=<remote>` instead."
85        )?;
86        let name_patterns: Vec<RemoteBookmarkNamePattern> = args
87            .names
88            .iter()
89            .map(|s| s.parse())
90            .try_collect()
91            .map_err(cli_error)?;
92        find_trackable_remote_bookmarks(ui, view, &name_patterns)?
93    } else {
94        let bookmark_expr = parse_union_name_patterns(ui, &args.names)?;
95        let remote_expr = match (&args.remotes, ignored_remote) {
96            (Some(text), _) => parse_union_name_patterns(ui, text)?,
97            (None, Some(ignored)) => StringExpression::exact(ignored).negated(),
98            (None, None) => StringExpression::all(),
99        };
100        let bookmark_matcher = bookmark_expr.to_matcher();
101        let remote_matcher = remote_expr.to_matcher();
102        let matched_refs =
103            trackable_remote_bookmarks_matching(view, &bookmark_matcher, &remote_matcher).collect();
104        warn_unmatched_local_or_remote_bookmarks(ui, view, &bookmark_expr)?;
105        warn_unmatched_remotes(ui, view, &remote_expr)?;
106        matched_refs
107    };
108    let mut symbols = Vec::new();
109    for (symbol, remote_ref) in matched_refs {
110        if ignored_remote.is_some_and(|ignored| symbol.remote == ignored) {
111            // This restriction can be lifted if we want to support untracked @git
112            // bookmarks.
113            writeln!(
114                ui.warning_default(),
115                "Git-tracking bookmark cannot be untracked: {symbol}"
116            )?;
117        } else if !remote_ref.is_tracked() {
118            writeln!(
119                ui.warning_default(),
120                "Remote bookmark not tracked yet: {symbol}"
121            )?;
122        } else {
123            symbols.push(symbol);
124        }
125    }
126    let mut tx = workspace_command.start_transaction();
127    for &symbol in &symbols {
128        tx.repo_mut().untrack_remote_bookmark(symbol);
129    }
130    if !symbols.is_empty() {
131        writeln!(
132            ui.status(),
133            "Stopped tracking {} remote bookmarks.",
134            symbols.len()
135        )?;
136    }
137    tx.finish(
138        ui,
139        format!("untrack remote bookmark {}", symbols.iter().join(", ")),
140    )?;
141    Ok(())
142}