just playing with tangled
at gvimdiff 226 lines 7.5 kB view raw
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 std::collections::HashSet; 16 17use clap_complete::ArgValueCandidates; 18use itertools::Itertools as _; 19use jj_lib::config::ConfigGetResultExt as _; 20use jj_lib::git; 21use jj_lib::git::GitFetch; 22use jj_lib::ref_name::RemoteName; 23use jj_lib::repo::Repo as _; 24use jj_lib::str_util::StringPattern; 25 26use crate::cli_util::CommandHelper; 27use crate::cli_util::WorkspaceCommandHelper; 28use crate::cli_util::WorkspaceCommandTransaction; 29use crate::command_error::config_error; 30use crate::command_error::user_error; 31use crate::command_error::CommandError; 32use crate::commands::git::get_single_remote; 33use crate::complete; 34use crate::git_util::print_git_import_stats; 35use crate::git_util::with_remote_git_callbacks; 36use crate::ui::Ui; 37 38/// Fetch from a Git remote 39/// 40/// If a working-copy commit gets abandoned, it will be given a new, empty 41/// commit. This is true in general; it is not specific to this command. 42#[derive(clap::Args, Clone, Debug)] 43pub struct GitFetchArgs { 44 /// Fetch only some of the branches 45 /// 46 /// By default, the specified name matches exactly. Use `glob:` prefix to 47 /// expand `*` as a glob, e.g. `--branch 'glob:push-*'`. Other wildcard 48 /// characters such as `?` are *not* supported. 49 #[arg( 50 long, short, 51 alias = "bookmark", 52 default_value = "glob:*", 53 value_parser = StringPattern::parse, 54 add = ArgValueCandidates::new(complete::bookmarks), 55 )] 56 branch: Vec<StringPattern>, 57 /// The remote to fetch from (only named remotes are supported, can be 58 /// repeated) 59 /// 60 /// This defaults to the `git.fetch` setting. If that is not configured, and 61 /// if there are multiple remotes, the remote named "origin" will be used. 62 /// 63 /// By default, the specified remote names matches exactly. Use a [string 64 /// pattern], e.g. `--remote 'glob:*'`, to select remotes using 65 /// patterns. 66 /// 67 /// [string pattern]: 68 /// https://jj-vcs.github.io/jj/latest/revsets#string-patterns 69 #[arg( 70 long = "remote", 71 value_name = "REMOTE", 72 value_parser = StringPattern::parse, 73 add = ArgValueCandidates::new(complete::git_remotes), 74 )] 75 remotes: Vec<StringPattern>, 76 /// Fetch from all remotes 77 #[arg(long, conflicts_with = "remotes")] 78 all_remotes: bool, 79} 80 81#[tracing::instrument(skip_all)] 82pub fn cmd_git_fetch( 83 ui: &mut Ui, 84 command: &CommandHelper, 85 args: &GitFetchArgs, 86) -> Result<(), CommandError> { 87 let mut workspace_command = command.workspace_helper(ui)?; 88 let remote_patterns = if args.all_remotes { 89 vec![StringPattern::everything()] 90 } else if args.remotes.is_empty() { 91 get_default_fetch_remotes(ui, &workspace_command)? 92 } else { 93 args.remotes.clone() 94 }; 95 96 let all_remotes = git::get_all_remote_names(workspace_command.repo().store())?; 97 98 let mut matching_remotes = HashSet::new(); 99 let mut unmatched_patterns = Vec::new(); 100 for pattern in remote_patterns { 101 let remotes = all_remotes 102 .iter() 103 .filter(|r| pattern.matches(r.as_str())) 104 .collect_vec(); 105 if remotes.is_empty() { 106 unmatched_patterns.push(pattern); 107 } else { 108 matching_remotes.extend(remotes); 109 } 110 } 111 112 match &unmatched_patterns[..] { 113 [] => {} // Everything matched, all good 114 [pattern] if pattern.is_exact() => { 115 return Err(user_error(format!("No git remote named '{pattern}'"))) 116 } 117 patterns => { 118 return Err(user_error(format!( 119 "No matching git remotes for patterns: {}", 120 patterns.iter().join(", ") 121 ))) 122 } 123 } 124 125 let remotes = all_remotes 126 .iter() 127 .filter(|r| matching_remotes.contains(r)) 128 .map(|r| r.as_ref()) 129 .collect_vec(); 130 131 let mut tx = workspace_command.start_transaction(); 132 do_git_fetch(ui, &mut tx, &remotes, &args.branch)?; 133 tx.finish( 134 ui, 135 format!( 136 "fetch from git remote(s) {}", 137 remotes.iter().map(|n| n.as_symbol()).join(",") 138 ), 139 )?; 140 Ok(()) 141} 142 143const DEFAULT_REMOTE: &RemoteName = RemoteName::new("origin"); 144 145fn get_default_fetch_remotes( 146 ui: &Ui, 147 workspace_command: &WorkspaceCommandHelper, 148) -> Result<Vec<StringPattern>, CommandError> { 149 const KEY: &str = "git.fetch"; 150 let settings = workspace_command.settings(); 151 if let Ok(remotes) = settings.get::<Vec<String>>(KEY) { 152 remotes 153 .into_iter() 154 .map(|r| parse_remote_pattern(&r)) 155 .try_collect() 156 } else if let Some(remote) = settings.get_string(KEY).optional()? { 157 Ok(vec![parse_remote_pattern(&remote)?]) 158 } else if let Some(remote) = get_single_remote(workspace_command.repo().store())? { 159 // if nothing was explicitly configured, try to guess 160 if remote != DEFAULT_REMOTE { 161 writeln!( 162 ui.hint_default(), 163 "Fetching from the only existing remote: {remote}", 164 remote = remote.as_symbol() 165 )?; 166 } 167 Ok(vec![StringPattern::exact(remote)]) 168 } else { 169 Ok(vec![StringPattern::exact(DEFAULT_REMOTE)]) 170 } 171} 172 173fn parse_remote_pattern(remote: &str) -> Result<StringPattern, CommandError> { 174 StringPattern::parse(remote).map_err(config_error) 175} 176 177fn do_git_fetch( 178 ui: &mut Ui, 179 tx: &mut WorkspaceCommandTransaction, 180 remotes: &[&RemoteName], 181 branch_names: &[StringPattern], 182) -> Result<(), CommandError> { 183 let git_settings = tx.settings().git_settings()?; 184 let mut git_fetch = GitFetch::new(tx.repo_mut(), &git_settings)?; 185 186 for remote_name in remotes { 187 with_remote_git_callbacks(ui, |callbacks| { 188 git_fetch.fetch(remote_name, branch_names, callbacks, None) 189 })?; 190 } 191 let import_stats = git_fetch.import_refs()?; 192 print_git_import_stats(ui, tx.repo(), &import_stats, true)?; 193 warn_if_branches_not_found(ui, tx, branch_names, remotes) 194} 195 196fn warn_if_branches_not_found( 197 ui: &mut Ui, 198 tx: &WorkspaceCommandTransaction, 199 branches: &[StringPattern], 200 remotes: &[&RemoteName], 201) -> Result<(), CommandError> { 202 for branch in branches { 203 let matches = remotes.iter().any(|&remote| { 204 let remote = StringPattern::exact(remote); 205 tx.repo() 206 .view() 207 .remote_bookmarks_matching(branch, &remote) 208 .next() 209 .is_some() 210 || tx 211 .base_repo() 212 .view() 213 .remote_bookmarks_matching(branch, &remote) 214 .next() 215 .is_some() 216 }); 217 if !matches { 218 writeln!( 219 ui.warning_default(), 220 "No branch matching `{branch}` found on any specified/configured remote", 221 )?; 222 } 223 } 224 225 Ok(()) 226}