Skip to content

Commit

Permalink
Merge branch 'tracking-branch'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Dec 18, 2023
2 parents 3c65ea3 + 530c15d commit 0fe20e8
Show file tree
Hide file tree
Showing 33 changed files with 1,014 additions and 219 deletions.
8 changes: 8 additions & 0 deletions gitoxide-core/src/repository/revision/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub struct Options {
pub cat_file: bool,
pub tree_mode: TreeMode,
pub blob_format: BlobFormat,
pub show_reference: bool,
}

pub enum TreeMode {
Expand Down Expand Up @@ -46,6 +47,7 @@ pub(crate) mod function {
cat_file,
tree_mode,
blob_format,
show_reference,
}: Options,
) -> anyhow::Result<()> {
repo.object_cache_size_if_unset(1024 * 1024);
Expand Down Expand Up @@ -77,6 +79,12 @@ pub(crate) mod function {
if cat_file {
return display_object(&repo, spec, tree_mode, cache.as_mut().map(|c| (blob_format, c)), out);
}
if let Some(r) = spec.first_reference().filter(|_| show_reference) {
writeln!(out, "{}", r.name)?;
}
if let Some(r) = spec.second_reference().filter(|_| show_reference) {
writeln!(out, "{}", r.name)?;
}
writeln!(out, "{spec}", spec = spec.detach())?;
}
}
Expand Down
24 changes: 16 additions & 8 deletions gix-refspec/src/match_group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@ impl<'a> MatchGroup<'a> {
specs: specs.into_iter().filter(|s| s.op == Operation::Fetch).collect(),
}
}

/// Take all the push ref specs from `specs` get a match group ready.
pub fn from_push_specs(specs: impl IntoIterator<Item = RefSpecRef<'a>>) -> Self {
MatchGroup {
specs: specs.into_iter().filter(|s| s.op == Operation::Push).collect(),
}
}
}

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

let mut has_negation = false;
for (spec_index, (spec, matcher)) in self.specs.iter().zip(matchers.iter_mut()).enumerate() {
if spec.mode == Mode::Negative {
has_negation = true;
continue;
}
for (item_index, item) in items.clone().enumerate() {
if spec.mode == Mode::Negative {
has_negation = true;
continue;
}
if let Some(matcher) = matcher {
let (matched, rhs) = matcher.matches_lhs(item);
if matched {
Expand All @@ -73,8 +81,8 @@ impl<'a> MatchGroup<'a> {
}
}

if let Some(id) = has_negation.then(|| items.next().map(|i| i.target)).flatten() {
let null_id = gix_hash::ObjectId::null(id.kind());
if let Some(hash_kind) = has_negation.then(|| items.next().map(|i| i.target.kind())).flatten() {
let null_id = hash_kind.null();
for matcher in matchers
.into_iter()
.zip(self.specs.iter())
Expand Down
6 changes: 2 additions & 4 deletions gix-refspec/src/match_group/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@ pub struct Matcher<'a> {

impl<'a> Matcher<'a> {
/// Match `item` against this spec and return `(true, Some<rhs>)` to gain the other side of the match as configured, or `(true, None)`
/// if there was no `rhs`.
/// if there was no `rhs` but the `item` matched. Lastly, return `(false, None)` if `item` didn't match at all.
///
/// This may involve resolving a glob with an allocation, as the destination is built using the matching portion of a glob.
pub fn matches_lhs(&self, item: Item<'_>) -> (bool, Option<Cow<'a, BStr>>) {
match (self.lhs, self.rhs) {
(Some(lhs), None) => (lhs.matches(item).is_match(), None),
(Some(lhs), Some(rhs)) => lhs.matches(item).into_match_outcome(rhs, item),
(None, _) => {
unreachable!("For all we know, the lefthand side is never empty. Push specs might change that.")
}
(None, _) => (false, None),
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions gix/src/config/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pub(crate) mod root {
pub const PACK: sections::Pack = sections::Pack;
/// The `protocol` section.
pub const PROTOCOL: sections::Protocol = sections::Protocol;
/// The `push` section.
pub const PUSH: sections::Push = sections::Push;
/// The `remote` section.
pub const REMOTE: sections::Remote = sections::Remote;
/// The `safe` section.
Expand Down Expand Up @@ -83,6 +85,7 @@ pub(crate) mod root {
&Self::MAILMAP,
&Self::PACK,
&Self::PROTOCOL,
&Self::PUSH,
&Self::REMOTE,
&Self::SAFE,
&Self::SSH,
Expand All @@ -95,9 +98,9 @@ pub(crate) mod root {

mod sections;
pub use sections::{
branch, checkout, core, credential, extensions, fetch, gitoxide, http, index, protocol, remote, ssh, Author,
branch, checkout, core, credential, extensions, fetch, gitoxide, http, index, protocol, push, remote, ssh, Author,
Branch, Checkout, Clone, Committer, Core, Credential, Extensions, Fetch, Gitoxide, Http, Index, Init, Mailmap,
Pack, Protocol, Remote, Safe, Ssh, Url, User,
Pack, Protocol, Push, Remote, Safe, Ssh, Url, User,
};
#[cfg(feature = "blob-diff")]
pub use sections::{diff, Diff};
Expand Down
5 changes: 5 additions & 0 deletions gix/src/config/tree/sections/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ pub mod pack;
pub struct Protocol;
pub mod protocol;

/// The `push` top-level section.
#[derive(Copy, Clone, Default)]
pub struct Push;
pub mod push;

/// The `remote` top-level section.
#[derive(Copy, Clone, Default)]
pub struct Remote;
Expand Down
64 changes: 64 additions & 0 deletions gix/src/config/tree/sections/push.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::{
config,
config::tree::{keys, Key, Push, Section},
};

impl Push {
/// The `push.default` key
pub const DEFAULT: Default = Default::new_with_validate("default", &config::Tree::PUSH, validate::Default);
}

impl Section for Push {
fn name(&self) -> &str {
"push"
}

fn keys(&self) -> &[&dyn Key] {
&[&Self::DEFAULT]
}
}

/// The `remote.<name>.tagOpt` key type.
pub type Default = keys::Any<validate::Default>;

mod default {
use std::borrow::Cow;

use crate::{
bstr::{BStr, ByteSlice},
config,
config::tree::push::Default,
push,
};

impl Default {
/// Try to interpret `value` as `push.default`.
pub fn try_into_default(
&'static self,
value: Cow<'_, BStr>,
) -> Result<push::Default, config::key::GenericErrorWithValue> {
Ok(match value.as_ref().as_bytes() {
b"nothing" => push::Default::Nothing,
b"current" => push::Default::Current,
b"upstream" | b"tracking" => push::Default::Upstream,
b"simple" => push::Default::Simple,
b"matching" => push::Default::Matching,
_ => return Err(config::key::GenericErrorWithValue::from_value(self, value.into_owned())),
})
}
}
}

mod validate {
pub struct Default;
use std::{borrow::Cow, error::Error};

use crate::{bstr::BStr, config::tree::keys::Validate};

impl Validate for Default {
fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
super::Push::DEFAULT.try_into_default(Cow::Borrowed(value))?;
Ok(())
}
}
}
2 changes: 2 additions & 0 deletions gix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ pub mod tag;

///
pub mod progress;
///
pub mod push;

///
pub mod diff;
Expand Down
21 changes: 21 additions & 0 deletions gix/src/push.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// All possible values of `push.default`.
#[derive(Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)]
pub enum Default {
/// Do not push anything unless a refspec is provided explicitly.
///
/// This is for safety.
Nothing,
/// Push the current branch to update a remote branch with the same name.
Current,
/// Push the current branch to the branch it would fetch from and merge with,
/// i.e. what is configured in `branch.<name>.merge`, retrievable with
/// the `@{upstream}` refspec.
Upstream,
/// Push the current branch with the same name to the remote.
/// This is the same as [`Current`](Default::Current), but fails if
/// `branch.<name>.merge` is set to a branch that is named differently.
#[default]
Simple,
/// Push *all* branches to their similarly named counterpart on the remote.
Matching,
}
66 changes: 32 additions & 34 deletions gix/src/reference/remote.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,47 @@
use crate::{config, config::tree::Branch, remote, Reference};
use crate::repository::{branch_remote_ref_name, branch_remote_tracking_ref_name};
use crate::{remote, Reference};
use gix_ref::FullNameRef;
use std::borrow::Cow;

/// Remotes
impl<'repo> Reference<'repo> {
/// Find the unvalidated name of our remote for `direction` as configured in `branch.<name>.remote|pushRemote` respectively.
/// If `Some(<name>)` it can be used in [`Repository::find_remote(…)`][crate::Repository::find_remote()], or if `None` then
/// [`Repository::remote_default_name()`][crate::Repository::remote_default_name()] could be used in its place.
///
/// Find the name of our remote for `direction` as configured in `branch.<name>.remote|pushRemote` respectively.
/// Return `None` if no remote is configured.
///
/// # Note
///
/// - it's recommended to use the [`remote(…)`][Self::remote()] method as it will configure the remote with additional
/// information.
/// - `branch.<name>.pushRemote` falls back to `branch.<name>.remote`.
/// See also [`Repository::branch_remote_name()`](crate::Repository::branch_remote_name()) for more details.
pub fn remote_name(&self, direction: remote::Direction) -> Option<remote::Name<'repo>> {
let name = self.name().shorten();
let config = &self.repo.config.resolved;
(direction == remote::Direction::Push)
.then(|| {
config
.string("branch", Some(name), Branch::PUSH_REMOTE.name)
.or_else(|| config.string("remote", None, config::tree::Remote::PUSH_DEFAULT.name))
})
.flatten()
.or_else(|| config.string("branch", Some(name), Branch::REMOTE.name))
.and_then(|name| name.try_into().ok())
self.repo.branch_remote_name(self.name().shorten(), direction)
}

/// Like [`remote_name(…)`][Self::remote_name()], but configures the returned `Remote` with additional information like
/// Find the remote along with all configuration associated with it suitable for handling this reference.
///
/// - `branch.<name>.merge` to know which branch on the remote side corresponds to this one for merging when pulling.
///
/// It also handles if the remote is a configured URL, which has no name.
/// See also [`Repository::branch_remote()`](crate::Repository::branch_remote()) for more details.
pub fn remote(
&self,
direction: remote::Direction,
) -> Option<Result<crate::Remote<'repo>, remote::find::existing::Error>> {
// TODO: use `branch.<name>.merge`
self.remote_name(direction).map(|name| match name {
remote::Name::Symbol(name) => self.repo.find_remote(name.as_ref()).map_err(Into::into),
remote::Name::Url(url) => gix_url::parse(url.as_ref()).map_err(Into::into).and_then(|url| {
self.repo
.remote_at(url)
.map_err(|err| remote::find::existing::Error::Find(remote::find::Error::Init(err)))
}),
})
self.repo.branch_remote(self.name().shorten(), direction)
}

/// Return the name of this reference on the remote side.
///
/// See [`Repository::branch_remote_ref_name()`](crate::Repository::branch_remote_ref_name()) for details.
#[doc(alias = "upstream", alias = "git2")]
pub fn remote_ref_name(
&self,
direction: remote::Direction,
) -> Option<Result<Cow<'_, FullNameRef>, branch_remote_ref_name::Error>> {
self.repo.branch_remote_ref_name(self.name(), direction)
}

/// Return the name of the reference that tracks this reference on the remote side.
///
/// See [`Repository::branch_remote_tracking_ref_name()`](crate::Repository::branch_remote_tracking_ref_name()) for details.
#[doc(alias = "upstream", alias = "git2")]
pub fn remote_tracking_ref_name(
&self,
direction: remote::Direction,
) -> Option<Result<Cow<'_, FullNameRef>, branch_remote_tracking_ref_name::Error>> {
self.repo.branch_remote_tracking_ref_name(self.name(), direction)
}
}
2 changes: 1 addition & 1 deletion gix/src/remote/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl Direction {
}

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

0 comments on commit 0fe20e8

Please sign in to comment.