Skip to content

Commit 0fe20e8

Browse files
committed
Merge branch 'tracking-branch'
2 parents 3c65ea3 + 530c15d commit 0fe20e8

File tree

33 files changed

+1014
-219
lines changed

33 files changed

+1014
-219
lines changed

gitoxide-core/src/repository/revision/resolve.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub struct Options {
66
pub cat_file: bool,
77
pub tree_mode: TreeMode,
88
pub blob_format: BlobFormat,
9+
pub show_reference: bool,
910
}
1011

1112
pub enum TreeMode {
@@ -46,6 +47,7 @@ pub(crate) mod function {
4647
cat_file,
4748
tree_mode,
4849
blob_format,
50+
show_reference,
4951
}: Options,
5052
) -> anyhow::Result<()> {
5153
repo.object_cache_size_if_unset(1024 * 1024);
@@ -77,6 +79,12 @@ pub(crate) mod function {
7779
if cat_file {
7880
return display_object(&repo, spec, tree_mode, cache.as_mut().map(|c| (blob_format, c)), out);
7981
}
82+
if let Some(r) = spec.first_reference().filter(|_| show_reference) {
83+
writeln!(out, "{}", r.name)?;
84+
}
85+
if let Some(r) = spec.second_reference().filter(|_| show_reference) {
86+
writeln!(out, "{}", r.name)?;
87+
}
8088
writeln!(out, "{spec}", spec = spec.detach())?;
8189
}
8290
}

gix-refspec/src/match_group/mod.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@ impl<'a> MatchGroup<'a> {
1616
specs: specs.into_iter().filter(|s| s.op == Operation::Fetch).collect(),
1717
}
1818
}
19+
20+
/// Take all the push ref specs from `specs` get a match group ready.
21+
pub fn from_push_specs(specs: impl IntoIterator<Item = RefSpecRef<'a>>) -> Self {
22+
MatchGroup {
23+
specs: specs.into_iter().filter(|s| s.op == Operation::Push).collect(),
24+
}
25+
}
1926
}
2027

2128
/// Matching
2229
impl<'a> MatchGroup<'a> {
23-
/// Match all `items` against all fetch specs present in this group, returning deduplicated mappings from source to destination.
24-
/// Note that this method only makes sense if the specs are indeed fetch specs and may panic otherwise.
30+
/// Match all `items` against all *fetch* specs present in this group, returning deduplicated mappings from source to destination.
31+
/// *Note that this method is correct only for specs*, even though it also *works for push-specs*.
2532
///
2633
/// Note that negative matches are not part of the return value, so they are not observable but will be used to remove mappings.
34+
// TODO: figure out how to deal with push-specs, probably when push is being implemented.
2735
pub fn match_remotes<'item>(self, mut items: impl Iterator<Item = Item<'item>> + Clone) -> Outcome<'a, 'item> {
2836
let mut out = Vec::new();
2937
let mut seen = BTreeSet::default();
@@ -54,11 +62,11 @@ impl<'a> MatchGroup<'a> {
5462

5563
let mut has_negation = false;
5664
for (spec_index, (spec, matcher)) in self.specs.iter().zip(matchers.iter_mut()).enumerate() {
65+
if spec.mode == Mode::Negative {
66+
has_negation = true;
67+
continue;
68+
}
5769
for (item_index, item) in items.clone().enumerate() {
58-
if spec.mode == Mode::Negative {
59-
has_negation = true;
60-
continue;
61-
}
6270
if let Some(matcher) = matcher {
6371
let (matched, rhs) = matcher.matches_lhs(item);
6472
if matched {
@@ -73,8 +81,8 @@ impl<'a> MatchGroup<'a> {
7381
}
7482
}
7583

76-
if let Some(id) = has_negation.then(|| items.next().map(|i| i.target)).flatten() {
77-
let null_id = gix_hash::ObjectId::null(id.kind());
84+
if let Some(hash_kind) = has_negation.then(|| items.next().map(|i| i.target.kind())).flatten() {
85+
let null_id = hash_kind.null();
7886
for matcher in matchers
7987
.into_iter()
8088
.zip(self.specs.iter())

gix-refspec/src/match_group/util.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,14 @@ pub struct Matcher<'a> {
1313

1414
impl<'a> Matcher<'a> {
1515
/// Match `item` against this spec and return `(true, Some<rhs>)` to gain the other side of the match as configured, or `(true, None)`
16-
/// if there was no `rhs`.
16+
/// if there was no `rhs` but the `item` matched. Lastly, return `(false, None)` if `item` didn't match at all.
1717
///
1818
/// This may involve resolving a glob with an allocation, as the destination is built using the matching portion of a glob.
1919
pub fn matches_lhs(&self, item: Item<'_>) -> (bool, Option<Cow<'a, BStr>>) {
2020
match (self.lhs, self.rhs) {
2121
(Some(lhs), None) => (lhs.matches(item).is_match(), None),
2222
(Some(lhs), Some(rhs)) => lhs.matches(item).into_match_outcome(rhs, item),
23-
(None, _) => {
24-
unreachable!("For all we know, the lefthand side is never empty. Push specs might change that.")
25-
}
23+
(None, _) => (false, None),
2624
}
2725
}
2826
}

gix/src/config/tree/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ pub(crate) mod root {
5151
pub const PACK: sections::Pack = sections::Pack;
5252
/// The `protocol` section.
5353
pub const PROTOCOL: sections::Protocol = sections::Protocol;
54+
/// The `push` section.
55+
pub const PUSH: sections::Push = sections::Push;
5456
/// The `remote` section.
5557
pub const REMOTE: sections::Remote = sections::Remote;
5658
/// The `safe` section.
@@ -83,6 +85,7 @@ pub(crate) mod root {
8385
&Self::MAILMAP,
8486
&Self::PACK,
8587
&Self::PROTOCOL,
88+
&Self::PUSH,
8689
&Self::REMOTE,
8790
&Self::SAFE,
8891
&Self::SSH,
@@ -95,9 +98,9 @@ pub(crate) mod root {
9598

9699
mod sections;
97100
pub use sections::{
98-
branch, checkout, core, credential, extensions, fetch, gitoxide, http, index, protocol, remote, ssh, Author,
101+
branch, checkout, core, credential, extensions, fetch, gitoxide, http, index, protocol, push, remote, ssh, Author,
99102
Branch, Checkout, Clone, Committer, Core, Credential, Extensions, Fetch, Gitoxide, Http, Index, Init, Mailmap,
100-
Pack, Protocol, Remote, Safe, Ssh, Url, User,
103+
Pack, Protocol, Push, Remote, Safe, Ssh, Url, User,
101104
};
102105
#[cfg(feature = "blob-diff")]
103106
pub use sections::{diff, Diff};

gix/src/config/tree/sections/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ pub mod pack;
8686
pub struct Protocol;
8787
pub mod protocol;
8888

89+
/// The `push` top-level section.
90+
#[derive(Copy, Clone, Default)]
91+
pub struct Push;
92+
pub mod push;
93+
8994
/// The `remote` top-level section.
9095
#[derive(Copy, Clone, Default)]
9196
pub struct Remote;

gix/src/config/tree/sections/push.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use crate::{
2+
config,
3+
config::tree::{keys, Key, Push, Section},
4+
};
5+
6+
impl Push {
7+
/// The `push.default` key
8+
pub const DEFAULT: Default = Default::new_with_validate("default", &config::Tree::PUSH, validate::Default);
9+
}
10+
11+
impl Section for Push {
12+
fn name(&self) -> &str {
13+
"push"
14+
}
15+
16+
fn keys(&self) -> &[&dyn Key] {
17+
&[&Self::DEFAULT]
18+
}
19+
}
20+
21+
/// The `remote.<name>.tagOpt` key type.
22+
pub type Default = keys::Any<validate::Default>;
23+
24+
mod default {
25+
use std::borrow::Cow;
26+
27+
use crate::{
28+
bstr::{BStr, ByteSlice},
29+
config,
30+
config::tree::push::Default,
31+
push,
32+
};
33+
34+
impl Default {
35+
/// Try to interpret `value` as `push.default`.
36+
pub fn try_into_default(
37+
&'static self,
38+
value: Cow<'_, BStr>,
39+
) -> Result<push::Default, config::key::GenericErrorWithValue> {
40+
Ok(match value.as_ref().as_bytes() {
41+
b"nothing" => push::Default::Nothing,
42+
b"current" => push::Default::Current,
43+
b"upstream" | b"tracking" => push::Default::Upstream,
44+
b"simple" => push::Default::Simple,
45+
b"matching" => push::Default::Matching,
46+
_ => return Err(config::key::GenericErrorWithValue::from_value(self, value.into_owned())),
47+
})
48+
}
49+
}
50+
}
51+
52+
mod validate {
53+
pub struct Default;
54+
use std::{borrow::Cow, error::Error};
55+
56+
use crate::{bstr::BStr, config::tree::keys::Validate};
57+
58+
impl Validate for Default {
59+
fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
60+
super::Push::DEFAULT.try_into_default(Cow::Borrowed(value))?;
61+
Ok(())
62+
}
63+
}
64+
}

gix/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ pub mod tag;
187187

188188
///
189189
pub mod progress;
190+
///
191+
pub mod push;
190192

191193
///
192194
pub mod diff;

gix/src/push.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// All possible values of `push.default`.
2+
#[derive(Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)]
3+
pub enum Default {
4+
/// Do not push anything unless a refspec is provided explicitly.
5+
///
6+
/// This is for safety.
7+
Nothing,
8+
/// Push the current branch to update a remote branch with the same name.
9+
Current,
10+
/// Push the current branch to the branch it would fetch from and merge with,
11+
/// i.e. what is configured in `branch.<name>.merge`, retrievable with
12+
/// the `@{upstream}` refspec.
13+
Upstream,
14+
/// Push the current branch with the same name to the remote.
15+
/// This is the same as [`Current`](Default::Current), but fails if
16+
/// `branch.<name>.merge` is set to a branch that is named differently.
17+
#[default]
18+
Simple,
19+
/// Push *all* branches to their similarly named counterpart on the remote.
20+
Matching,
21+
}

gix/src/reference/remote.rs

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,47 @@
1-
use crate::{config, config::tree::Branch, remote, Reference};
1+
use crate::repository::{branch_remote_ref_name, branch_remote_tracking_ref_name};
2+
use crate::{remote, Reference};
3+
use gix_ref::FullNameRef;
4+
use std::borrow::Cow;
25

36
/// Remotes
47
impl<'repo> Reference<'repo> {
5-
/// Find the unvalidated name of our remote for `direction` as configured in `branch.<name>.remote|pushRemote` respectively.
6-
/// If `Some(<name>)` it can be used in [`Repository::find_remote(…)`][crate::Repository::find_remote()], or if `None` then
7-
/// [`Repository::remote_default_name()`][crate::Repository::remote_default_name()] could be used in its place.
8-
///
8+
/// Find the name of our remote for `direction` as configured in `branch.<name>.remote|pushRemote` respectively.
99
/// Return `None` if no remote is configured.
1010
///
11-
/// # Note
12-
///
13-
/// - it's recommended to use the [`remote(…)`][Self::remote()] method as it will configure the remote with additional
14-
/// information.
15-
/// - `branch.<name>.pushRemote` falls back to `branch.<name>.remote`.
11+
/// See also [`Repository::branch_remote_name()`](crate::Repository::branch_remote_name()) for more details.
1612
pub fn remote_name(&self, direction: remote::Direction) -> Option<remote::Name<'repo>> {
17-
let name = self.name().shorten();
18-
let config = &self.repo.config.resolved;
19-
(direction == remote::Direction::Push)
20-
.then(|| {
21-
config
22-
.string("branch", Some(name), Branch::PUSH_REMOTE.name)
23-
.or_else(|| config.string("remote", None, config::tree::Remote::PUSH_DEFAULT.name))
24-
})
25-
.flatten()
26-
.or_else(|| config.string("branch", Some(name), Branch::REMOTE.name))
27-
.and_then(|name| name.try_into().ok())
13+
self.repo.branch_remote_name(self.name().shorten(), direction)
2814
}
2915

30-
/// Like [`remote_name(…)`][Self::remote_name()], but configures the returned `Remote` with additional information like
16+
/// Find the remote along with all configuration associated with it suitable for handling this reference.
3117
///
32-
/// - `branch.<name>.merge` to know which branch on the remote side corresponds to this one for merging when pulling.
33-
///
34-
/// It also handles if the remote is a configured URL, which has no name.
18+
/// See also [`Repository::branch_remote()`](crate::Repository::branch_remote()) for more details.
3519
pub fn remote(
3620
&self,
3721
direction: remote::Direction,
3822
) -> Option<Result<crate::Remote<'repo>, remote::find::existing::Error>> {
39-
// TODO: use `branch.<name>.merge`
40-
self.remote_name(direction).map(|name| match name {
41-
remote::Name::Symbol(name) => self.repo.find_remote(name.as_ref()).map_err(Into::into),
42-
remote::Name::Url(url) => gix_url::parse(url.as_ref()).map_err(Into::into).and_then(|url| {
43-
self.repo
44-
.remote_at(url)
45-
.map_err(|err| remote::find::existing::Error::Find(remote::find::Error::Init(err)))
46-
}),
47-
})
23+
self.repo.branch_remote(self.name().shorten(), direction)
24+
}
25+
26+
/// Return the name of this reference on the remote side.
27+
///
28+
/// See [`Repository::branch_remote_ref_name()`](crate::Repository::branch_remote_ref_name()) for details.
29+
#[doc(alias = "upstream", alias = "git2")]
30+
pub fn remote_ref_name(
31+
&self,
32+
direction: remote::Direction,
33+
) -> Option<Result<Cow<'_, FullNameRef>, branch_remote_ref_name::Error>> {
34+
self.repo.branch_remote_ref_name(self.name(), direction)
35+
}
36+
37+
/// Return the name of the reference that tracks this reference on the remote side.
38+
///
39+
/// See [`Repository::branch_remote_tracking_ref_name()`](crate::Repository::branch_remote_tracking_ref_name()) for details.
40+
#[doc(alias = "upstream", alias = "git2")]
41+
pub fn remote_tracking_ref_name(
42+
&self,
43+
direction: remote::Direction,
44+
) -> Option<Result<Cow<'_, FullNameRef>, branch_remote_tracking_ref_name::Error>> {
45+
self.repo.branch_remote_tracking_ref_name(self.name(), direction)
4846
}
4947
}

gix/src/remote/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ impl Direction {
2222
}
2323

2424
/// The name of a remote, either interpreted as symbol like `origin` or as url as returned by [`Remote::name()`][crate::Remote::name()].
25-
#[derive(Debug, PartialEq, Eq, Clone)]
25+
#[derive(Debug, PartialEq, Eq, Clone, Ord, PartialOrd, Hash)]
2626
pub enum Name<'repo> {
2727
/// A symbolic name, like `origin`.
2828
/// Note that it has not necessarily been validated yet.

0 commit comments

Comments
 (0)