Skip to content

Commit eace6c5

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. The `fetch_tags()` accessor allows to query this information, and the `with_fetch_tags()` builder method allwos 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.
1 parent 4eb842c commit eace6c5

File tree

10 files changed

+99
-12
lines changed

10 files changed

+99
-12
lines changed

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/errors.rs

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ pub mod find {
66
#[derive(Debug, thiserror::Error)]
77
#[allow(missing_docs)]
88
pub enum Error {
9+
#[error(
10+
"The value for 'remote.<name>.tagOpt` is invalid and must either be '--tags' or '--no-tags': \"{value}\""
11+
)]
12+
TagOpt { value: BString },
913
#[error("{spec:?} {kind} ref-spec failed to parse")]
1014
RefSpec {
1115
spec: BString,

git-repository/src/remote/fetch.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use crate::bstr::{BStr, BString};
2-
31
/// If `Yes`, don't really make changes but do as much as possible to get an idea of what would be done.
42
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
53
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
@@ -20,8 +18,28 @@ pub(crate) enum WritePackedRefs {
2018
Only,
2119
}
2220

21+
/// Describe how to handle tags when fetching
22+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
23+
pub enum Tags {
24+
/// Fetch all tags from the remote, even if these are not reachable from objects referred to by our refspecs.
25+
All,
26+
/// Fetch only the tags that point to the objects being sent.
27+
/// That way, annotated tags that point to an object we receive are automatically transmitted and their refs are created.
28+
/// The same goes for lightweight tags.
29+
Included,
30+
/// Do not fetch any tags.
31+
None,
32+
}
33+
34+
impl Default for Tags {
35+
fn default() -> Self {
36+
Tags::Included
37+
}
38+
}
39+
2340
/// Information about the relationship between our refspecs, and remote references with their local counterparts.
2441
#[derive(Default, Debug, Clone)]
42+
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
2543
pub struct RefMap {
2644
/// A mapping between a remote reference and a local tracking branch.
2745
pub mappings: Vec<Mapping>,
@@ -41,13 +59,15 @@ pub struct RefMap {
4159

4260
/// Either an object id that the remote has or the matched remote ref itself.
4361
#[derive(Debug, Clone)]
62+
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
4463
pub enum Source {
4564
/// An object id, as the matched ref-spec was an object id itself.
4665
ObjectId(git_hash::ObjectId),
4766
/// The remote reference that matched the ref-specs name.
4867
Ref(git_protocol::handshake::Ref),
4968
}
5069

70+
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
5171
impl Source {
5272
/// Return either the direct object id we refer to or the direct target that a reference refers to.
5373
/// The latter may be a direct or a symbolic reference, and we degenerate this to the peeled object id.
@@ -60,7 +80,7 @@ impl Source {
6080
}
6181

6282
/// Return ourselves as the full name of the reference we represent, or `None` if this source isn't a reference but an object.
63-
pub fn as_name(&self) -> Option<&BStr> {
83+
pub fn as_name(&self) -> Option<&crate::bstr::BStr> {
6484
match self {
6585
Source::ObjectId(_) => None,
6686
Source::Ref(r) => match r {
@@ -75,11 +95,12 @@ impl Source {
7595

7696
/// A mapping between a single remote reference and its advertised objects to a local destination which may or may not exist.
7797
#[derive(Debug, Clone)]
98+
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
7899
pub struct Mapping {
79100
/// The reference on the remote side, along with information about the objects they point to as advertised by the server.
80101
pub remote: Source,
81102
/// The local tracking reference to update after fetching the object visible via `remote`.
82-
pub local: Option<BString>,
103+
pub local: Option<crate::bstr::BString>,
83104
/// The index into the fetch ref-specs used to produce the mapping, allowing it to be recovered.
84105
pub spec_index: usize,
85106
}

git-repository/src/remote/init.rs

+4
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ use crate::bstr::BString;
2727

2828
/// Initialization
2929
impl<'repo> Remote<'repo> {
30+
#[allow(clippy::too_many_arguments)]
3031
pub(crate) fn from_preparsed_config(
3132
name_or_url: Option<BString>,
3233
url: Option<git_url::Url>,
3334
push_url: Option<git_url::Url>,
3435
fetch_specs: Vec<RefSpec>,
3536
push_specs: Vec<RefSpec>,
3637
should_rewrite_urls: bool,
38+
fetch_tags: remote::fetch::Tags,
3739
repo: &'repo Repository,
3840
) -> Result<Self, Error> {
3941
debug_assert!(
@@ -51,6 +53,7 @@ impl<'repo> Remote<'repo> {
5153
push_url_alias,
5254
fetch_specs,
5355
push_specs,
56+
fetch_tags,
5457
repo,
5558
})
5659
}
@@ -76,6 +79,7 @@ impl<'repo> Remote<'repo> {
7679
push_url_alias: None,
7780
fetch_specs: Vec::new(),
7881
push_specs: Vec::new(),
82+
fetch_tags: Default::default(),
7983
repo,
8084
})
8185
}

git-repository/src/remote/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ pub use errors::find;
4343
pub mod init;
4444

4545
///
46-
#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
4746
pub mod fetch;
4847

4948
///

git-repository/src/repository/remote.rs

+22-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ use crate::{bstr::BStr, remote, remote::find, Remote};
44

55
impl crate::Repository {
66
/// Create a new remote available at the given `url`.
7+
///
8+
/// It's configured to fetch included tags by default, similar to git.
9+
/// See [`with_fetch_tags(…)`][Remote::with_fetch_tags()] for a way to change it.
710
pub fn remote_at<Url, E>(&self, url: Url) -> Result<Remote<'_>, remote::init::Error>
811
where
912
Url: TryInto<git_url::Url, Error = E>,
@@ -12,7 +15,8 @@ impl crate::Repository {
1215
Remote::from_fetch_url(url, true, self)
1316
}
1417

15-
/// Create a new remote available at the given `url`, but don't rewrite the url according to rewrite rules.
18+
/// Create a new remote available at the given `url` similarly to [`remote_at()`][crate::Repository::remote_at()],
19+
/// but don't rewrite the url according to rewrite rules.
1620
/// This eliminates a failure mode in case the rewritten URL is faulty, allowing to selectively [apply rewrite
1721
/// rules][Remote::rewrite_urls()] later and do so non-destructively.
1822
pub fn remote_at_without_url_rewrite<Url, E>(&self, url: Url) -> Result<Remote<'_>, remote::init::Error>
@@ -91,14 +95,14 @@ impl crate::Repository {
9195
};
9296
let url = config_url("url", "fetch");
9397
let push_url = config_url("pushUrl", "push");
98+
let config = &self.config.resolved;
9499

95100
let mut config_spec = |op: git_refspec::parse::Operation| {
96101
let kind = match op {
97102
git_refspec::parse::Operation::Fetch => "fetch",
98103
git_refspec::parse::Operation::Push => "push",
99104
};
100-
self.config
101-
.resolved
105+
config
102106
.strings_filter("remote", Some(name_or_url), kind, &mut filter)
103107
.map(|specs| {
104108
specs
@@ -122,6 +126,20 @@ impl crate::Repository {
122126
};
123127
let fetch_specs = config_spec(git_refspec::parse::Operation::Fetch);
124128
let push_specs = config_spec(git_refspec::parse::Operation::Push);
129+
let fetch_tags = config
130+
.string_filter("remote", Some(name_or_url), "tagOpt", &mut filter)
131+
.map(|tag| {
132+
Ok(match tag.as_ref().as_ref() {
133+
b"--tags" => remote::fetch::Tags::All,
134+
b"--no-tags" => remote::fetch::Tags::None,
135+
unknown => return Err(find::Error::TagOpt { value: unknown.into() }),
136+
})
137+
});
138+
let fetch_tags = match fetch_tags {
139+
Some(Ok(v)) => v,
140+
Some(Err(err)) => return Some(Err(err)),
141+
None => Default::default(),
142+
};
125143

126144
match (url, fetch_specs, push_url, push_specs) {
127145
(None, None, None, None) => None,
@@ -156,6 +174,7 @@ impl crate::Repository {
156174
fetch_specs,
157175
push_specs,
158176
rewrite_urls,
177+
fetch_tags,
159178
self,
160179
)
161180
.map_err(Into::into),

git-repository/src/types.rs

+2
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ pub struct Remote<'repo> {
195195
pub(crate) fetch_specs: Vec<git_refspec::RefSpec>,
196196
/// Refspecs for use when pushing.
197197
pub(crate) push_specs: Vec<git_refspec::RefSpec>,
198+
/// Tell us what to do with tags when fetched.
199+
pub(crate) fetch_tags: remote::fetch::Tags,
198200
// /// Delete local tracking branches that don't exist on the remote anymore.
199201
// pub(crate) prune: bool,
200202
// /// Delete tags that don't exist on the remote anymore, equivalent to pruning the refspec `refs/tags/*:refs/tags/*`.

git-repository/tests/fixtures/make_remote_repos.sh

+6
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ git clone --shared base clone
9797
git remote add myself .
9898
)
9999

100+
git clone --no-tags --shared base clone-no-tags
101+
(cd clone-no-tags
102+
git remote add --no-tags myself-no-tags .
103+
git remote add --tags myself-with-tags .
104+
)
105+
100106
git clone --shared base push-default
101107
(cd push-default
102108

git-repository/tests/repository/remote.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,21 @@ mod find_remote {
110110
use crate::remote;
111111

112112
#[test]
113-
fn typical() {
113+
fn tags_option() -> crate::Result {
114+
let repo = remote::repo("clone-no-tags");
115+
for (remote_name, expected) in [
116+
("origin", git::remote::fetch::Tags::None),
117+
("myself-no-tags", git::remote::fetch::Tags::None),
118+
("myself-with-tags", git::remote::fetch::Tags::All),
119+
] {
120+
let remote = repo.find_remote(remote_name)?;
121+
assert_eq!(remote.fetch_tags(), expected, "specifically set in this repo");
122+
}
123+
Ok(())
124+
}
125+
126+
#[test]
127+
fn typical() -> crate::Result {
114128
let repo = remote::repo("clone");
115129
let mut count = 0;
116130
let base_dir = base_dir(&repo);
@@ -120,11 +134,17 @@ mod find_remote {
120134
];
121135
for (name, (url, refspec)) in repo.remote_names().into_iter().zip(expected) {
122136
count += 1;
123-
let remote = repo.find_remote(name).expect("no error");
137+
let remote = repo.find_remote(name)?;
124138
assert_eq!(remote.name().expect("set").as_bstr(), name);
125139

126-
let url = git::url::parse(url.into()).expect("valid");
127-
assert_eq!(remote.url(Direction::Fetch).unwrap(), &url);
140+
assert_eq!(
141+
remote.fetch_tags(),
142+
git::remote::fetch::Tags::Included,
143+
"the default value as it's not specified"
144+
);
145+
146+
let url = git::url::parse(url.into())?;
147+
assert_eq!(remote.url(Direction::Fetch).expect("present"), &url);
128148

129149
assert_eq!(
130150
remote.refspecs(Direction::Fetch),
@@ -142,6 +162,7 @@ mod find_remote {
142162
repo.find_remote("unknown").unwrap_err(),
143163
git::remote::find::existing::Error::NotFound { .. }
144164
));
165+
Ok(())
145166
}
146167

147168
#[test]

0 commit comments

Comments
 (0)