Skip to content

Commit b726b7c

Browse files
committed
feat: Remote knows about its tagOpt configuration.
That way it's clear if it should or shouldn't fetch included/reachable tags automatically. The default setting for this is to include tags, similar to `git`. The `fetch_tags()` accessor allows to query this information, and the `with_fetch_tags()` builder method allows to set the value comfortably right after creating the `Remote` instance. The `tagOpt` key will also be written as part of the remote's git configuration. Clone operations can set the `Tags` setting when configuring the remote in a callback.
1 parent b527ddb commit b726b7c

File tree

22 files changed

+302
-71
lines changed

22 files changed

+302
-71
lines changed

git-repository/src/clone/fetch/mod.rs

+27-18
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ pub enum Error {
1212
#[error(transparent)]
1313
Fetch(#[from] crate::remote::fetch::Error),
1414
#[error(transparent)]
15-
RemoteConfiguration(#[from] crate::remote::init::Error),
15+
RemoteInit(#[from] crate::remote::init::Error),
16+
#[error("Custom configuration of remote to clone from failed")]
17+
RemoteConfiguration(#[source] Box<dyn std::error::Error + Send + Sync>),
1618
#[error("Default remote configured at `clone.defaultRemoteName` is invalid")]
1719
RemoteName(#[from] crate::remote::name::Error),
1820
#[error("Failed to load repo-local git configuration before writing")]
@@ -24,7 +26,7 @@ pub enum Error {
2426
#[error("The remote HEAD points to a reference named {head_ref_name:?} which is invalid.")]
2527
InvalidHeadRef {
2628
source: git_validate::refname::Error,
27-
head_ref_name: crate::bstr::BString,
29+
head_ref_name: BString,
2830
},
2931
#[error("Failed to update HEAD with values from remote")]
3032
HeadUpdate(#[from] crate::reference::edit::Error),
@@ -51,6 +53,7 @@ impl PrepareFetch {
5153
P: crate::Progress,
5254
P::SubProgress: 'static,
5355
{
56+
use crate::remote;
5457
use crate::{bstr::ByteVec, remote::fetch::RefLogMessage};
5558

5659
let repo = self
@@ -64,36 +67,39 @@ impl PrepareFetch {
6467
.config
6568
.resolved
6669
.string_by_key("clone.defaultRemoteName")
67-
.map(|n| crate::remote::name::validated(n.into_owned()))
70+
.map(|n| remote::name::validated(n.into_owned()))
6871
.unwrap_or_else(|| Ok("origin".into()))?,
6972
};
7073

7174
let mut remote = repo
7275
.remote_at(self.url.clone())?
7376
.with_refspecs(
7477
Some(format!("+refs/heads/*:refs/remotes/{remote_name}/*").as_str()),
75-
crate::remote::Direction::Fetch,
78+
remote::Direction::Fetch,
7679
)
7780
.expect("valid static spec");
7881
if let Some(f) = self.configure_remote.as_mut() {
79-
remote = f(remote)?;
82+
remote = f(remote).map_err(|err| Error::RemoteConfiguration(err))?;
8083
}
8184

8285
let config = util::write_remote_to_local_config_file(&mut remote, remote_name.clone())?;
8386

8487
// Add HEAD after the remote was written to config, we need it to know what to checkout later, and assure
8588
// the ref that HEAD points to is present no matter what.
86-
remote.fetch_specs.push(
87-
git_refspec::parse(
88-
format!("HEAD:refs/remotes/{remote_name}/HEAD").as_str().into(),
89-
git_refspec::parse::Operation::Fetch,
90-
)
91-
.expect("valid")
92-
.to_owned(),
93-
);
94-
let pending_pack: crate::remote::fetch::Prepare<'_, '_, _, _> = remote
95-
.connect(crate::remote::Direction::Fetch, progress)?
96-
.prepare_fetch(self.fetch_options.clone())?;
89+
let head_refspec = git_refspec::parse(
90+
format!("HEAD:refs/remotes/{remote_name}/HEAD").as_str().into(),
91+
git_refspec::parse::Operation::Fetch,
92+
)
93+
.expect("valid")
94+
.to_owned();
95+
let pending_pack: remote::fetch::Prepare<'_, '_, _, _> =
96+
remote.connect(remote::Direction::Fetch, progress)?.prepare_fetch({
97+
let mut opts = self.fetch_options.clone();
98+
if !opts.extra_refspecs.contains(&head_refspec) {
99+
opts.extra_refspecs.push(head_refspec)
100+
}
101+
opts
102+
})?;
97103
if pending_pack.ref_map().object_hash != repo.object_hash() {
98104
unimplemented!("configure repository to expect a different object hash as advertised by the server")
99105
}
@@ -148,10 +154,13 @@ impl PrepareFetch {
148154
///
149155
/// The passed in `remote` will be un-named and pre-configured to be a default remote as we know it from git-clone.
150156
/// It is not yet present in the configuration of the repository,
151-
/// but each change it will eventually be written to the configuration prior to performing a the fetch operation.
157+
/// but each change it will eventually be written to the configuration prior to performing a the fetch operation,
158+
/// _all changes done in `f()` will be persisted_.
159+
///
160+
/// It can also be used to configure additional options, like those for fetching tags.
152161
pub fn configure_remote(
153162
mut self,
154-
f: impl FnMut(crate::Remote<'_>) -> Result<crate::Remote<'_>, crate::remote::init::Error> + 'static,
163+
f: impl FnMut(crate::Remote<'_>) -> Result<crate::Remote<'_>, Box<dyn std::error::Error + Send + Sync>> + 'static,
155164
) -> Self {
156165
self.configure_remote = Some(Box::new(f));
157166
self

git-repository/src/clone/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use std::convert::TryInto;
22

33
use crate::bstr::BString;
44

5-
type ConfigureRemoteFn = Box<dyn FnMut(crate::Remote<'_>) -> Result<crate::Remote<'_>, crate::remote::init::Error>>;
5+
type ConfigureRemoteFn =
6+
Box<dyn FnMut(crate::Remote<'_>) -> Result<crate::Remote<'_>, Box<dyn std::error::Error + Send + Sync>>>;
67

78
/// A utility to collect configuration on how to fetch from a remote and initiate a fetch operation. It will delete the newly
89
/// created repository on when dropped without successfully finishing a fetch.

git-repository/src/remote/access.rs

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ impl<'repo> Remote<'repo> {
2222
}
2323
}
2424

25+
/// Return how we handle tags when fetching the remote.
26+
pub fn fetch_tags(&self) -> remote::fetch::Tags {
27+
self.fetch_tags
28+
}
29+
2530
/// Return the url used for the given `direction` with rewrites from `url.<base>.insteadOf|pushInsteadOf`, unless the instance
2631
/// was created with one of the `_without_url_rewrite()` methods.
2732
/// For pushing, this is the `remote.<name>.pushUrl` or the `remote.<name>.url` used for fetching, and for fetching it's

git-repository/src/remote/build.rs

+6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ impl Remote<'_> {
2323
self.push_url_inner(url, false)
2424
}
2525

26+
/// Configure how tags should be handled when fetching from the remote.
27+
pub fn with_fetch_tags(mut self, tags: remote::fetch::Tags) -> Self {
28+
self.fetch_tags = tags;
29+
self
30+
}
31+
2632
fn push_url_inner<Url, E>(mut self, push_url: Url, should_rewrite_urls: bool) -> Result<Self, remote::init::Error>
2733
where
2834
Url: TryInto<git_url::Url, Error = E>,

git-repository/src/remote/connection/fetch/receive_pack.rs

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ where
175175
.unwrap_or_else(|| RefLogMessage::Prefixed { action: "fetch".into() }),
176176
&self.ref_map.mappings,
177177
con.remote.refspecs(remote::Direction::Fetch),
178+
&self.ref_map.extra_refspecs,
178179
self.dry_run,
179180
self.write_packed_refs,
180181
)?;

git-repository/src/remote/connection/fetch/update_refs/mod.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl From<update::Mode> for Update {
3333
}
3434
}
3535

36-
/// Update all refs as derived from `mappings` and produce an `Outcome` informing about all applied changes in detail, with each
36+
/// Update all refs as derived from `refmap.mappings` and produce an `Outcome` informing about all applied changes in detail, with each
3737
/// [`update`][Update] corresponding to the [`fetch::Mapping`] of at the same index.
3838
/// If `dry_run` is true, ref transactions won't actually be applied, but are assumed to work without error so the underlying
3939
/// `repo` is not actually changed. Also it won't perform an 'object exists' check as these are likely not to exist as the pack
@@ -46,18 +46,24 @@ pub(crate) fn update(
4646
message: RefLogMessage,
4747
mappings: &[fetch::Mapping],
4848
refspecs: &[git_refspec::RefSpec],
49+
extra_refspecs: &[git_refspec::RefSpec],
4950
dry_run: fetch::DryRun,
5051
write_packed_refs: fetch::WritePackedRefs,
5152
) -> Result<update::Outcome, update::Error> {
5253
let mut edits = Vec::new();
5354
let mut updates = Vec::new();
5455

56+
// TODO: handle impclit tags according to the remote's tagopt - we can't write them as their destinations don't exist.
5557
for (remote, local, spec) in mappings.iter().filter_map(
5658
|fetch::Mapping {
5759
remote,
5860
local,
5961
spec_index,
60-
}| refspecs.get(*spec_index).map(|spec| (remote, local, spec)),
62+
}| {
63+
spec_index
64+
.get(refspecs, &extra_refspecs)
65+
.map(|spec| (remote, local, spec))
66+
},
6167
) {
6268
let remote_id = match remote.as_id() {
6369
Some(id) => id,

git-repository/src/remote/connection/fetch/update_refs/tests.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod update {
3333
}
3434
use git_ref::{transaction::Change, TargetRef};
3535

36+
use crate::remote::fetch::SpecIndex;
3637
use crate::{
3738
bstr::BString,
3839
remote::{
@@ -128,6 +129,7 @@ mod update {
128129
prefixed("action"),
129130
&mapping,
130131
&specs,
132+
&[],
131133
reflog_message.map(|_| fetch::DryRun::Yes).unwrap_or(fetch::DryRun::No),
132134
fetch::WritePackedRefs::Never,
133135
)
@@ -190,6 +192,7 @@ mod update {
190192
prefixed("action"),
191193
&mappings,
192194
&specs,
195+
&[],
193196
fetch::DryRun::Yes,
194197
fetch::WritePackedRefs::Never,
195198
)?;
@@ -219,6 +222,7 @@ mod update {
219222
prefixed("action"),
220223
&mappings,
221224
&specs,
225+
&[],
222226
fetch::DryRun::Yes,
223227
fetch::WritePackedRefs::Never,
224228
)
@@ -246,13 +250,14 @@ mod update {
246250
object: hex_to_id("f99771fe6a1b535783af3163eba95a927aae21d5"),
247251
}),
248252
local: Some("refs/heads/symbolic".into()),
249-
spec_index: 0,
253+
spec_index: SpecIndex::ExplicitInRemote(0),
250254
});
251255
let out = fetch::refs::update(
252256
&repo,
253257
prefixed("action"),
254258
&mappings,
255259
&specs,
260+
&[],
256261
fetch::DryRun::Yes,
257262
fetch::WritePackedRefs::Never,
258263
)
@@ -294,6 +299,7 @@ mod update {
294299
prefixed("action"),
295300
&mappings,
296301
&specs,
302+
&[],
297303
fetch::DryRun::Yes,
298304
fetch::WritePackedRefs::Never,
299305
)
@@ -318,6 +324,7 @@ mod update {
318324
prefixed("action"),
319325
&mappings,
320326
&specs,
327+
&[],
321328
fetch::DryRun::Yes,
322329
fetch::WritePackedRefs::Never,
323330
)
@@ -359,13 +366,14 @@ mod update {
359366
object: hex_to_id("f99771fe6a1b535783af3163eba95a927aae21d5"),
360367
}),
361368
local: Some("refs/remotes/origin/main".into()),
362-
spec_index: 0,
369+
spec_index: SpecIndex::ExplicitInRemote(0),
363370
});
364371
let out = fetch::refs::update(
365372
&repo,
366373
prefixed("action"),
367374
&mappings,
368375
&specs,
376+
&[],
369377
fetch::DryRun::Yes,
370378
fetch::WritePackedRefs::Never,
371379
)
@@ -412,6 +420,7 @@ mod update {
412420
},
413421
&mappings,
414422
&specs,
423+
&[],
415424
fetch::DryRun::Yes,
416425
fetch::WritePackedRefs::Never,
417426
)
@@ -444,6 +453,7 @@ mod update {
444453
prefixed("action"),
445454
&mappings,
446455
&specs,
456+
&[],
447457
fetch::DryRun::No,
448458
fetch::WritePackedRefs::Never,
449459
)
@@ -464,6 +474,7 @@ mod update {
464474
prefixed("prefix"),
465475
&mappings,
466476
&specs,
477+
&[],
467478
fetch::DryRun::No,
468479
fetch::WritePackedRefs::Never,
469480
)
@@ -495,6 +506,7 @@ mod update {
495506
prefixed("prefix"),
496507
&mappings,
497508
&specs,
509+
&[],
498510
fetch::DryRun::No,
499511
fetch::WritePackedRefs::Never,
500512
)
@@ -536,7 +548,7 @@ mod update {
536548
_ => unreachable!("not a ref, must be id: {:?}", m),
537549
}),
538550
local: m.rhs.map(|r| r.into_owned()),
539-
spec_index: m.spec_index,
551+
spec_index: SpecIndex::ExplicitInRemote(m.spec_index),
540552
})
541553
.collect();
542554
(mappings, vec![spec.to_owned()])

git-repository/src/remote/connection/fetch/update_refs/update.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ impl Outcome {
101101
&self,
102102
mappings: &'a [fetch::Mapping],
103103
refspecs: &'b [git_refspec::RefSpec],
104+
extra_refspecs: &'b [git_refspec::RefSpec],
104105
) -> impl Iterator<
105106
Item = (
106107
&super::Update,
@@ -113,7 +114,7 @@ impl Outcome {
113114
(
114115
update,
115116
mapping,
116-
refspecs.get(mapping.spec_index),
117+
mapping.spec_index.get(refspecs, extra_refspecs),
117118
update.edit_index.and_then(|idx| self.edits.get(idx)),
118119
)
119120
})

0 commit comments

Comments
 (0)