Unified · Split
| @@ -52,7 +52,9 @@ impl PrepareFetch { · 51 unmodified lines | ||
| 52 | 52 | P::SubProgress: 'static, |
| 53 | 53 | { |
| 54 | 54 | use crate::bstr::{BStr, ByteVec}; |
| 55 | + use crate::remote::fetch::RefLogMessage; | |
| 55 | 56 | use git_ref::transaction::{LogChange, RefLog}; |
| 57 | + | |
| 56 | 58 | fn write_remote_to_local_config_file( |
| 57 | 59 | remote: &mut crate::Remote<'_>, |
| 58 | 60 | remote_name: String, |
| @@ -207,15 +209,18 @@ impl PrepareFetch { · 148 unmodified lines | ||
| 207 | 209 | if pending_pack.ref_map().object_hash != repo.object_hash() { |
| 208 | 210 | unimplemented!("configure repository to expect a different object hash as advertised by the server") |
| 209 | 211 | } |
| 210 | − let outcome = pending_pack.receive(should_interrupt)?; | |
| 211 | − | |
| 212 | − replace_changed_local_config_file(repo, config); | |
| 213 | − | |
| 214 | 212 | let reflog_message = { |
| 215 | 213 | let mut b = self.url.to_bstring(); |
| 216 | 214 | b.insert_str(0, "clone: from "); |
| 217 | 215 | b |
| 218 | 216 | }; |
| 217 | + let outcome = pending_pack | |
| 218 | + .with_reflog_message(RefLogMessage::Override { | |
| 219 | + message: reflog_message.clone(), | |
| 220 | + }) | |
| 221 | + .receive(should_interrupt)?; | |
| 222 | + | |
| 223 | + replace_changed_local_config_file(repo, config); | |
| 219 | 224 | update_head(repo, &outcome.ref_map.remote_refs, reflog_message.as_ref())?; |
| 220 | 225 | |
| 221 | 226 | Ok((self.repo.take().expect("still present"), outcome)) |
| @@ -0,0 +1,31 @@ | ||
| 1 | +/// The error returned by [`receive()`](super::Prepare::receive()). | |
| 2 | +#[derive(Debug, thiserror::Error)] | |
| 3 | +#[allow(missing_docs)] | |
| 4 | +pub enum Error { | |
| 5 | + #[error("{message}{}", desired.map(|n| format!(" (got {})", n)).unwrap_or_default())] | |
| 6 | + Configuration { | |
| 7 | + message: &'static str, | |
| 8 | + desired: Option<i64>, | |
| 9 | + source: Option<git_config::value::Error>, | |
| 10 | + }, | |
| 11 | + #[error("Could not decode server reply")] | |
| 12 | + FetchResponse(#[from] git_protocol::fetch::response::Error), | |
| 13 | + #[error("Cannot fetch from a remote that uses {remote} while local repository uses {local} for object hashes")] | |
| 14 | + IncompatibleObjectHash { | |
| 15 | + local: git_hash::Kind, | |
| 16 | + remote: git_hash::Kind, | |
| 17 | + }, | |
| 18 | + #[error(transparent)] | |
| 19 | + Negotiate(#[from] super::negotiate::Error), | |
| 20 | + #[error(transparent)] | |
| 21 | + Client(#[from] git_protocol::transport::client::Error), | |
| 22 | + #[error(transparent)] | |
| 23 | + WritePack(#[from] git_pack::bundle::write::Error), | |
| 24 | + #[error(transparent)] | |
| 25 | + UpdateRefs(#[from] super::refs::update::Error), | |
| 26 | + #[error("Failed to remove .keep file at \"{}\"", path.display())] | |
| 27 | + RemovePackKeepFile { | |
| 28 | + path: std::path::PathBuf, | |
| 29 | + source: std::io::Error, | |
| 30 | + }, | |
| 31 | +} | |
| @@ -1,5 +1,6 @@ | ||
| 1 | 1 | use std::sync::atomic::AtomicBool; |
| 2 | 2 | |
| 3 | +use crate::bstr::BString; | |
| 3 | 4 | use git_odb::FindExt; |
| 4 | 5 | use git_protocol::transport::client::Transport; |
| 5 | 6 | |
| @@ -13,40 +14,31 @@ use crate::{ · 7 unmodified lines | ||
| 13 | 14 | Progress, |
| 14 | 15 | }; |
| 15 | 16 | |
| 16 | −mod error { | |
| 17 | − /// The error returned by [`receive()`](super::Prepare::receive()). | |
| 18 | − #[derive(Debug, thiserror::Error)] | |
| 19 | − #[allow(missing_docs)] | |
| 20 | − pub enum Error { | |
| 21 | − #[error("{message}{}", desired.map(|n| format!(" (got {})", n)).unwrap_or_default())] | |
| 22 | − Configuration { | |
| 23 | − message: &'static str, | |
| 24 | − desired: Option<i64>, | |
| 25 | − source: Option<git_config::value::Error>, | |
| 26 | − }, | |
| 27 | − #[error("Could not decode server reply")] | |
| 28 | − FetchResponse(#[from] git_protocol::fetch::response::Error), | |
| 29 | − #[error("Cannot fetch from a remote that uses {remote} while local repository uses {local} for object hashes")] | |
| 30 | − IncompatibleObjectHash { | |
| 31 | − local: git_hash::Kind, | |
| 32 | − remote: git_hash::Kind, | |
| 33 | − }, | |
| 34 | − #[error(transparent)] | |
| 35 | − Negotiate(#[from] super::negotiate::Error), | |
| 36 | − #[error(transparent)] | |
| 37 | − Client(#[from] git_protocol::transport::client::Error), | |
| 38 | − #[error(transparent)] | |
| 39 | − WritePack(#[from] git_pack::bundle::write::Error), | |
| 40 | − #[error(transparent)] | |
| 41 | − UpdateRefs(#[from] super::refs::update::Error), | |
| 42 | − #[error("Failed to remove .keep file at \"{}\"", path.display())] | |
| 43 | − RemovePackKeepFile { | |
| 44 | − path: std::path::PathBuf, | |
| 45 | − source: std::io::Error, | |
| 46 | − }, | |
| 17 | +mod error; | |
| 18 | +pub use error::Error; | |
| 19 | + | |
| 20 | +/// The way reflog messages should be composed whenever a ref is written with recent objects from a remote. | |
| 21 | +pub enum RefLogMessage { | |
| 22 | + /// Prefix the log with `action` and generate the typical suffix as `git` would. | |
| 23 | + Prefixed { | |
| 24 | + /// The action to use, like `fetch` or `pull`. | |
| 25 | + action: String, | |
| 26 | + }, | |
| 27 | + /// Control the entire message, using `message` verbatim. | |
| 28 | + Override { | |
| 29 | + /// The complete reflog message. | |
| 30 | + message: BString, | |
| 31 | + }, | |
| 32 | +} | |
| 33 | + | |
| 34 | +impl RefLogMessage { | |
| 35 | + pub(crate) fn compose(&self, context: &str) -> BString { | |
| 36 | + match self { | |
| 37 | + RefLogMessage::Prefixed { action } => format!("{}: {}", action, context).into(), | |
| 38 | + RefLogMessage::Override { message } => message.to_owned(), | |
| 39 | + } | |
| 47 | 40 | } |
| 48 | 41 | } |
| 49 | −pub use error::Error; | |
| 50 | 42 | |
| 51 | 43 | /// The status of the repository after the fetch operation |
| 52 | 44 | #[derive(Debug, Clone)] |
| @@ -118,6 +110,7 @@ where · 65 unmodified lines | ||
| 118 | 110 | con: Some(self), |
| 119 | 111 | ref_map, |
| 120 | 112 | dry_run: DryRun::No, |
| 113 | + reflog_message: None, | |
| 121 | 114 | }) |
| 122 | 115 | } |
| 123 | 116 | } |
| @@ -267,7 +260,9 @@ where · 143 unmodified lines | ||
| 267 | 260 | |
| 268 | 261 | let update_refs = refs::update( |
| 269 | 262 | repo, |
| 270 | − "fetch", | |
| 263 | + self.reflog_message | |
| 264 | + .take() | |
| 265 | + .unwrap_or_else(|| RefLogMessage::Prefixed { action: "fetch".into() }), | |
| 271 | 266 | &self.ref_map.mappings, |
| 272 | 267 | con.remote.refspecs(remote::Direction::Fetch), |
| 273 | 268 | self.dry_run, |
| @@ -323,6 +318,7 @@ where · 49 unmodified lines | ||
| 323 | 318 | con: Option<Connection<'remote, 'repo, T, P>>, |
| 324 | 319 | ref_map: RefMap, |
| 325 | 320 | dry_run: DryRun, |
| 321 | + reflog_message: Option<RefLogMessage>, | |
| 326 | 322 | } |
| 327 | 323 | |
| 328 | 324 | /// Builder |
| @@ -337,6 +333,12 @@ where · 8 unmodified lines | ||
| 337 | 333 | self.dry_run = enabled.then(|| DryRun::Yes).unwrap_or(DryRun::No); |
| 338 | 334 | self |
| 339 | 335 | } |
| 336 | + | |
| 337 | + /// Set the reflog message to use when updating refs after fetching a pack. | |
| 338 | + pub fn with_reflog_message(mut self, reflog_message: RefLogMessage) -> Self { | |
| 339 | + self.reflog_message = reflog_message.into(); | |
| 340 | + self | |
| 341 | + } | |
| 340 | 342 | } |
| 341 | 343 | |
| 342 | 344 | impl<'remote, 'repo, T, P> Drop for Prepare<'remote, 'repo, T, P> |
| @@ -7,6 +7,7 @@ use git_ref::{ · 6 unmodified lines | ||
| 7 | 7 | Target, TargetRef, |
| 8 | 8 | }; |
| 9 | 9 | |
| 10 | +use crate::remote::fetch::RefLogMessage; | |
| 10 | 11 | use crate::{ |
| 11 | 12 | ext::ObjectIdExt, |
| 12 | 13 | remote::{fetch, fetch::refs::update::Mode}, |
| @@ -41,7 +42,7 @@ impl From<update::Mode> for Update { · 28 unmodified lines | ||
| 41 | 42 | /// It can be used to produce typical information that one is used to from `git fetch`. |
| 42 | 43 | pub(crate) fn update( |
| 43 | 44 | repo: &Repository, |
| 44 | − action: &str, | |
| 45 | + message: RefLogMessage, | |
| 45 | 46 | mappings: &[fetch::Mapping], |
| 46 | 47 | refspecs: &[git_refspec::RefSpec], |
| 47 | 48 | dry_run: fetch::DryRun, |
| @@ -161,7 +162,7 @@ pub(crate) fn update( · 113 unmodified lines | ||
| 161 | 162 | log: LogChange { |
| 162 | 163 | mode: RefLog::AndReference, |
| 163 | 164 | force_create_reflog: false, |
| 164 | − message: format!("{}: {}", action, reflog_message).into(), | |
| 165 | + message: message.compose(reflog_message), | |
| 165 | 166 | }, |
| 166 | 167 | expected: previous_value, |
| 167 | 168 | new: Target::Peeled(remote_id.into()), |
| @@ -29,9 +29,11 @@ mod update { · 28 unmodified lines | ||
| 29 | 29 | let repo = git::open_opts(dir.path().join(name), git::open::Options::isolated()).unwrap(); |
| 30 | 30 | (repo, dir) |
| 31 | 31 | } |
| 32 | + use crate::bstr::BString; | |
| 32 | 33 | use git_ref::{transaction::Change, TargetRef}; |
| 33 | 34 | |
| 34 | 35 | use crate::remote::fetch; |
| 36 | + use crate::remote::fetch::RefLogMessage; | |
| 35 | 37 | |
| 36 | 38 | #[test] |
| 37 | 39 | fn various_valid_updates() { |
| @@ -117,7 +119,7 @@ mod update { · 79 unmodified lines | ||
| 117 | 119 | let (mapping, specs) = mapping_from_spec(spec, &repo); |
| 118 | 120 | let out = fetch::refs::update( |
| 119 | 121 | &repo, |
| 120 | − "action", | |
| 122 | + prefixed("action"), | |
| 121 | 123 | &mapping, |
| 122 | 124 | &specs, |
| 123 | 125 | reflog_message.map(|_| fetch::DryRun::Yes).unwrap_or(fetch::DryRun::No), |
| @@ -176,7 +178,7 @@ mod update { · 52 unmodified lines | ||
| 176 | 178 | ] { |
| 177 | 179 | let spec = format!("refs/heads/main:refs/heads/{}", branch); |
| 178 | 180 | let (mappings, specs) = mapping_from_spec(&spec, &repo); |
| 179 | − let out = fetch::refs::update(&repo, "action", &mappings, &specs, fetch::DryRun::Yes)?; | |
| 181 | + let out = fetch::refs::update(&repo, prefixed("action"), &mappings, &specs, fetch::DryRun::Yes)?; | |
| 180 | 182 | |
| 181 | 183 | assert_eq!( |
| 182 | 184 | out.updates, |
| @@ -197,7 +199,7 @@ mod update { · 14 unmodified lines | ||
| 197 | 199 | fn symbolic_refs_are_never_written() { |
| 198 | 200 | let repo = repo("two-origins"); |
| 199 | 201 | let (mappings, specs) = mapping_from_spec("refs/heads/main:refs/heads/symbolic", &repo); |
| 200 | − let out = fetch::refs::update(&repo, "action", &mappings, &specs, fetch::DryRun::Yes).unwrap(); | |
| 202 | + let out = fetch::refs::update(&repo, prefixed("action"), &mappings, &specs, fetch::DryRun::Yes).unwrap(); | |
| 201 | 203 | |
| 202 | 204 | assert_eq!( |
| 203 | 205 | out.updates, |
| @@ -214,7 +216,17 @@ mod update { · 10 unmodified lines | ||
| 214 | 216 | fn non_fast_forward_is_rejected_but_appears_to_be_fast_forward_in_dryrun_mode() { |
| 215 | 217 | let repo = repo("two-origins"); |
| 216 | 218 | let (mappings, specs) = mapping_from_spec("refs/heads/main:refs/remotes/origin/g", &repo); |
| 217 | − let out = fetch::refs::update(&repo, "action", &mappings, &specs, fetch::DryRun::Yes).unwrap(); | |
| 219 | + let reflog_message: BString = "very special".into(); | |
| 220 | + let out = fetch::refs::update( | |
| 221 | + &repo, | |
| 222 | + RefLogMessage::Override { | |
| 223 | + message: reflog_message.clone(), | |
| 224 | + }, | |
| 225 | + &mappings, | |
| 226 | + &specs, | |
| 227 | + fetch::DryRun::Yes, | |
| 228 | + ) | |
| 229 | + .unwrap(); | |
| 218 | 230 | |
| 219 | 231 | assert_eq!( |
| 220 | 232 | out.updates, |
| @@ -225,13 +237,20 @@ mod update { · 4 unmodified lines | ||
| 225 | 237 | "The caller has to be aware and note that dry-runs can't know about fast-forwards as they don't have remote objects" |
| 226 | 238 | ); |
| 227 | 239 | assert_eq!(out.edits.len(), 1); |
| 240 | + let edit = &out.edits[0]; | |
| 241 | + match &edit.change { | |
| 242 | + Change::Update { log, .. } => { | |
| 243 | + assert_eq!(log.message, reflog_message); | |
| 244 | + } | |
| 245 | + _ => unreachable!("only updates"), | |
| 246 | + } | |
| 228 | 247 | } |
| 229 | 248 | |
| 230 | 249 | #[test] |
| 231 | 250 | fn non_fast_forward_is_rejected_if_dry_run_is_disabled() { |
| 232 | 251 | let (repo, _tmp) = repo_rw("two-origins"); |
| 233 | 252 | let (mappings, specs) = mapping_from_spec("refs/remotes/origin/g:refs/heads/not-currently-checked-out", &repo); |
| 234 | − let out = fetch::refs::update(&repo, "action", &mappings, &specs, fetch::DryRun::No).unwrap(); | |
| 253 | + let out = fetch::refs::update(&repo, prefixed("action"), &mappings, &specs, fetch::DryRun::No).unwrap(); | |
| 235 | 254 | |
| 236 | 255 | assert_eq!( |
| 237 | 256 | out.updates, |
| @@ -243,7 +262,7 @@ mod update { · 5 unmodified lines | ||
| 243 | 262 | assert_eq!(out.edits.len(), 0); |
| 244 | 263 | |
| 245 | 264 | let (mappings, specs) = mapping_from_spec("refs/heads/main:refs/remotes/origin/g", &repo); |
| 246 | − let out = fetch::refs::update(&repo, "prefix", &mappings, &specs, fetch::DryRun::No).unwrap(); | |
| 265 | + let out = fetch::refs::update(&repo, prefixed("prefix"), &mappings, &specs, fetch::DryRun::No).unwrap(); | |
| 247 | 266 | |
| 248 | 267 | assert_eq!( |
| 249 | 268 | out.updates, |
| @@ -266,7 +285,7 @@ mod update { · 16 unmodified lines | ||
| 266 | 285 | fn fast_forwards_are_called_out_even_if_force_is_given() { |
| 267 | 286 | let (repo, _tmp) = repo_rw("two-origins"); |
| 268 | 287 | let (mappings, specs) = mapping_from_spec("+refs/heads/main:refs/remotes/origin/g", &repo); |
| 269 | − let out = fetch::refs::update(&repo, "prefix", &mappings, &specs, fetch::DryRun::No).unwrap(); | |
| 288 | + let out = fetch::refs::update(&repo, prefixed("prefix"), &mappings, &specs, fetch::DryRun::No).unwrap(); | |
| 270 | 289 | |
| 271 | 290 | assert_eq!( |
| 272 | 291 | out.updates, |
| @@ -336,4 +355,8 @@ mod update { · 63 unmodified lines | ||
| 336 | 355 | object, |
| 337 | 356 | } |
| 338 | 357 | } |
| 358 | + | |
| 359 | + fn prefixed(action: &str) -> RefLogMessage { | |
| 360 | + RefLogMessage::Prefixed { action: action.into() } | |
| 361 | + } | |
| 339 | 362 | } |
| @@ -60,4 +60,4 @@ pub struct Mapping { · 59 unmodified lines | ||
| 60 | 60 | } |
| 61 | 61 | |
| 62 | 62 | #[cfg(feature = "blocking-network-client")] |
| 63 | −pub use super::connection::fetch::{negotiate, prepare, refs, Error, Outcome, Prepare, Status}; | |
| 63 | +pub use super::connection::fetch::{negotiate, prepare, refs, Error, Outcome, Prepare, RefLogMessage, Status}; | |
| @@ -70,6 +70,11 @@ mod blocking_io { · 69 unmodified lines | ||
| 70 | 70 | repo.objects.contains(edit.change.new_value().expect("always set").id()), |
| 71 | 71 | "part of the fetched pack" |
| 72 | 72 | ); |
| 73 | + let r = repo.find_reference(edit.name.as_ref()).expect("created"); | |
| 74 | + if r.name().category().expect("known") != git_ref::Category::Tag { | |
| 75 | + let mut logs = r.log_iter(); | |
| 76 | + assert_reflog(logs.all()); | |
| 77 | + } | |
| 73 | 78 | } |
| 74 | 79 | } |
| 75 | 80 | _ => unreachable!("clones are always causing changes and dry-runs aren't possible"), |
| @@ -107,7 +112,11 @@ mod blocking_io { · 31 unmodified lines | ||
| 107 | 112 | .unwrap(); |
| 108 | 113 | assert_eq!(logs.len(), 1, "just created"); |
| 109 | 114 | let line = &logs[0]; |
| 110 | − assert!(line.message.starts_with(b"clone: from "), "{:?} unexpected", line); | |
| 115 | + assert!( | |
| 116 | + line.message.starts_with(b"clone: from "), | |
| 117 | + "{:?} unexpected", | |
| 118 | + line.message | |
| 119 | + ); | |
| 111 | 120 | let path = git_path::from_bstr(line.message.rsplit(|b| *b == b' ').next().expect("path").as_bstr()); |
| 112 | 121 | assert!(path.is_absolute(), "{:?} must be absolute", path); |
| 113 | 122 | } |