History

proper reflog entries for all other updated refs during cloning (#450)

Sebastian Thiel committed 4 years ago

Commit ff4412e1 parent c25cb007 files · +119 −48Browse files

Unified · Split

7 files
  1. git-repository/src/clone/fetch.rs +9 −4
  2. git-repository/src/remote/connection/fetch/error.rs +31 −0
  3. git-repository/src/remote/connection/fetch/mod.rs +35 −33
  4. git-repository/src/remote/connection/fetch/update_refs/mod.rs +3 −2
  5. git-repository/src/remote/connection/fetch/update_refs/tests.rs +30 −7
  6. git-repository/src/remote/fetch.rs +1 −1
  7. git-repository/tests/clone/mod.rs +10 −1
git-repository/src/clone/fetch.rs+9−4
@@ -52,7 +52,9 @@ impl PrepareFetch { · 51 unmodified lines
5252 P::SubProgress: 'static,
5353 {
5454 use crate::bstr::{BStr, ByteVec};
55+ use crate::remote::fetch::RefLogMessage;
5556 use git_ref::transaction::{LogChange, RefLog};
57+
5658 fn write_remote_to_local_config_file(
5759 remote: &mut crate::Remote<'_>,
5860 remote_name: String,
@@ -207,15 +209,18 @@ impl PrepareFetch { · 148 unmodified lines
207209 if pending_pack.ref_map().object_hash != repo.object_hash() {
208210 unimplemented!("configure repository to expect a different object hash as advertised by the server")
209211 }
210− let outcome = pending_pack.receive(should_interrupt)?;
211−
212− replace_changed_local_config_file(repo, config);
213−
214212 let reflog_message = {
215213 let mut b = self.url.to_bstring();
216214 b.insert_str(0, "clone: from ");
217215 b
218216 };
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);
219224 update_head(repo, &outcome.ref_map.remote_refs, reflog_message.as_ref())?;
220225
221226 Ok((self.repo.take().expect("still present"), outcome))
git-repository/src/remote/connection/fetch/error.rs+31−0
@@ -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+}
git-repository/src/remote/connection/fetch/mod.rs+35−33
@@ -1,5 +1,6 @@
11 use std::sync::atomic::AtomicBool;
22
3+use crate::bstr::BString;
34 use git_odb::FindExt;
45 use git_protocol::transport::client::Transport;
56
@@ -13,40 +14,31 @@ use crate::{ · 7 unmodified lines
1314 Progress,
1415 };
1516
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+ }
4740 }
4841 }
49−pub use error::Error;
5042
5143 /// The status of the repository after the fetch operation
5244 #[derive(Debug, Clone)]
@@ -118,6 +110,7 @@ where · 65 unmodified lines
118110 con: Some(self),
119111 ref_map,
120112 dry_run: DryRun::No,
113+ reflog_message: None,
121114 })
122115 }
123116 }
@@ -267,7 +260,9 @@ where · 143 unmodified lines
267260
268261 let update_refs = refs::update(
269262 repo,
270− "fetch",
263+ self.reflog_message
264+ .take()
265+ .unwrap_or_else(|| RefLogMessage::Prefixed { action: "fetch".into() }),
271266 &self.ref_map.mappings,
272267 con.remote.refspecs(remote::Direction::Fetch),
273268 self.dry_run,
@@ -323,6 +318,7 @@ where · 49 unmodified lines
323318 con: Option<Connection<'remote, 'repo, T, P>>,
324319 ref_map: RefMap,
325320 dry_run: DryRun,
321+ reflog_message: Option<RefLogMessage>,
326322 }
327323
328324 /// Builder
@@ -337,6 +333,12 @@ where · 8 unmodified lines
337333 self.dry_run = enabled.then(|| DryRun::Yes).unwrap_or(DryRun::No);
338334 self
339335 }
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+ }
340342 }
341343
342344 impl<'remote, 'repo, T, P> Drop for Prepare<'remote, 'repo, T, P>
git-repository/src/remote/connection/fetch/update_refs/mod.rs+3−2
@@ -7,6 +7,7 @@ use git_ref::{ · 6 unmodified lines
77 Target, TargetRef,
88 };
99
10+use crate::remote::fetch::RefLogMessage;
1011 use crate::{
1112 ext::ObjectIdExt,
1213 remote::{fetch, fetch::refs::update::Mode},
@@ -41,7 +42,7 @@ impl From<update::Mode> for Update { · 28 unmodified lines
4142 /// It can be used to produce typical information that one is used to from `git fetch`.
4243 pub(crate) fn update(
4344 repo: &Repository,
44− action: &str,
45+ message: RefLogMessage,
4546 mappings: &[fetch::Mapping],
4647 refspecs: &[git_refspec::RefSpec],
4748 dry_run: fetch::DryRun,
@@ -161,7 +162,7 @@ pub(crate) fn update( · 113 unmodified lines
161162 log: LogChange {
162163 mode: RefLog::AndReference,
163164 force_create_reflog: false,
164− message: format!("{}: {}", action, reflog_message).into(),
165+ message: message.compose(reflog_message),
165166 },
166167 expected: previous_value,
167168 new: Target::Peeled(remote_id.into()),
git-repository/src/remote/connection/fetch/update_refs/tests.rs+30−7
@@ -29,9 +29,11 @@ mod update { · 28 unmodified lines
2929 let repo = git::open_opts(dir.path().join(name), git::open::Options::isolated()).unwrap();
3030 (repo, dir)
3131 }
32+ use crate::bstr::BString;
3233 use git_ref::{transaction::Change, TargetRef};
3334
3435 use crate::remote::fetch;
36+ use crate::remote::fetch::RefLogMessage;
3537
3638 #[test]
3739 fn various_valid_updates() {
@@ -117,7 +119,7 @@ mod update { · 79 unmodified lines
117119 let (mapping, specs) = mapping_from_spec(spec, &repo);
118120 let out = fetch::refs::update(
119121 &repo,
120− "action",
122+ prefixed("action"),
121123 &mapping,
122124 &specs,
123125 reflog_message.map(|_| fetch::DryRun::Yes).unwrap_or(fetch::DryRun::No),
@@ -176,7 +178,7 @@ mod update { · 52 unmodified lines
176178 ] {
177179 let spec = format!("refs/heads/main:refs/heads/{}", branch);
178180 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)?;
180182
181183 assert_eq!(
182184 out.updates,
@@ -197,7 +199,7 @@ mod update { · 14 unmodified lines
197199 fn symbolic_refs_are_never_written() {
198200 let repo = repo("two-origins");
199201 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();
201203
202204 assert_eq!(
203205 out.updates,
@@ -214,7 +216,17 @@ mod update { · 10 unmodified lines
214216 fn non_fast_forward_is_rejected_but_appears_to_be_fast_forward_in_dryrun_mode() {
215217 let repo = repo("two-origins");
216218 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();
218230
219231 assert_eq!(
220232 out.updates,
@@ -225,13 +237,20 @@ mod update { · 4 unmodified lines
225237 "The caller has to be aware and note that dry-runs can't know about fast-forwards as they don't have remote objects"
226238 );
227239 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+ }
228247 }
229248
230249 #[test]
231250 fn non_fast_forward_is_rejected_if_dry_run_is_disabled() {
232251 let (repo, _tmp) = repo_rw("two-origins");
233252 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();
235254
236255 assert_eq!(
237256 out.updates,
@@ -243,7 +262,7 @@ mod update { · 5 unmodified lines
243262 assert_eq!(out.edits.len(), 0);
244263
245264 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();
247266
248267 assert_eq!(
249268 out.updates,
@@ -266,7 +285,7 @@ mod update { · 16 unmodified lines
266285 fn fast_forwards_are_called_out_even_if_force_is_given() {
267286 let (repo, _tmp) = repo_rw("two-origins");
268287 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();
270289
271290 assert_eq!(
272291 out.updates,
@@ -336,4 +355,8 @@ mod update { · 63 unmodified lines
336355 object,
337356 }
338357 }
358+
359+ fn prefixed(action: &str) -> RefLogMessage {
360+ RefLogMessage::Prefixed { action: action.into() }
361+ }
339362 }
git-repository/src/remote/fetch.rs+1−1
@@ -60,4 +60,4 @@ pub struct Mapping { · 59 unmodified lines
6060 }
6161
6262 #[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};
git-repository/tests/clone/mod.rs+10−1
@@ -70,6 +70,11 @@ mod blocking_io { · 69 unmodified lines
7070 repo.objects.contains(edit.change.new_value().expect("always set").id()),
7171 "part of the fetched pack"
7272 );
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+ }
7378 }
7479 }
7580 _ => unreachable!("clones are always causing changes and dry-runs aren't possible"),
@@ -107,7 +112,11 @@ mod blocking_io { · 31 unmodified lines
107112 .unwrap();
108113 assert_eq!(logs.len(), 1, "just created");
109114 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+ );
111120 let path = git_path::from_bstr(line.message.rsplit(|b| *b == b' ').next().expect("path").as_bstr());
112121 assert!(path.is_absolute(), "{:?} must be absolute", path);
113122 }