From 5d5e2113a8353bf849ed4bfe9c8a341b8e00cd0b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 16 Sep 2022 19:30:27 +0800 Subject: [PATCH 01/32] sketch API for obtaining a reflist filtered by refspecs (#450) --- git-repository/src/remote/connection/list_refs.rs | 9 ++++++++- git-repository/src/remote/mod.rs | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/git-repository/src/remote/connection/list_refs.rs b/git-repository/src/remote/connection/list_refs.rs index f497ebdbfc7..6720e545e90 100644 --- a/git-repository/src/remote/connection/list_refs.rs +++ b/git-repository/src/remote/connection/list_refs.rs @@ -1,7 +1,7 @@ use git_features::progress::Progress; use git_protocol::transport::client::Transport; -use crate::remote::{connection::HandshakeWithRefs, Connection, Direction}; +use crate::remote::{connection::HandshakeWithRefs, fetch, Connection, Direction}; mod error { #[derive(Debug, thiserror::Error)] @@ -33,6 +33,13 @@ where Ok(res.refs) } + /// A mapping showing the objects available in refs matching our ref-specs on the remote side, along with their destination + /// ref locally, if set and if there are no conflicts. + #[git_protocol::maybe_async::maybe_async] + pub async fn ref_mapping(self) -> Result, Error> { + todo!() + } + #[git_protocol::maybe_async::maybe_async] async fn fetch_refs(&mut self) -> Result { let mut credentials_storage; diff --git a/git-repository/src/remote/mod.rs b/git-repository/src/remote/mod.rs index 9fc9666c66a..265ad79a4d7 100644 --- a/git-repository/src/remote/mod.rs +++ b/git-repository/src/remote/mod.rs @@ -25,6 +25,20 @@ pub use errors::find; /// pub mod init; +/// +#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] +pub mod fetch { + use crate::bstr::BString; + + /// A mapping between a single remote reference and its advertised objects to a local destination which may or may not exist. + pub struct Mapping { + /// The reference on the remote side, along with information about the objects they point to as advertised by the server. + pub remote: git_protocol::fetch::Ref, + /// The local tracking reference to update after fetching the object visible via `remote`. + pub local: Option, + } +} + /// #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] pub mod connect; From 6d1c37219cde7ce5c969d1581da4bb4593cfb1fd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 09:34:49 +0800 Subject: [PATCH 02/32] A more concrete sketch of how the API for obtaining mappings should look like (#450) --- .../src/remote/connection/list_refs.rs | 86 ++++++++++++------- git-repository/src/remote/connection/mod.rs | 4 +- git-repository/src/remote/mod.rs | 2 +- git-repository/tests/remote/list_refs.rs | 9 +- 4 files changed, 66 insertions(+), 35 deletions(-) diff --git a/git-repository/src/remote/connection/list_refs.rs b/git-repository/src/remote/connection/list_refs.rs index 6720e545e90..9d96eac0f19 100644 --- a/git-repository/src/remote/connection/list_refs.rs +++ b/git-repository/src/remote/connection/list_refs.rs @@ -1,22 +1,21 @@ use git_features::progress::Progress; use git_protocol::transport::client::Transport; -use crate::remote::{connection::HandshakeWithRefs, fetch, Connection, Direction}; +use crate::remote::{connection::HandshakeWithRefs, Connection, Direction}; -mod error { - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error(transparent)] - Handshake(#[from] git_protocol::fetch::handshake::Error), - #[error(transparent)] - ListRefs(#[from] git_protocol::fetch::refs::Error), - #[error(transparent)] - Transport(#[from] git_protocol::transport::client::Error), - #[error(transparent)] - ConfigureCredentials(#[from] crate::config::credential_helpers::Error), - } +/// The error returned by [`Connection::list_refs()`]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Handshake(#[from] git_protocol::fetch::handshake::Error), + #[error(transparent)] + ListRefs(#[from] git_protocol::fetch::refs::Error), + #[error(transparent)] + Transport(#[from] git_protocol::transport::client::Error), + #[error(transparent)] + ConfigureCredentials(#[from] crate::config::credential_helpers::Error), } -pub use error::Error; impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> where @@ -33,13 +32,6 @@ where Ok(res.refs) } - /// A mapping showing the objects available in refs matching our ref-specs on the remote side, along with their destination - /// ref locally, if set and if there are no conflicts. - #[git_protocol::maybe_async::maybe_async] - pub async fn ref_mapping(self) -> Result, Error> { - todo!() - } - #[git_protocol::maybe_async::maybe_async] async fn fetch_refs(&mut self) -> Result { let mut credentials_storage; @@ -75,15 +67,49 @@ where }; Ok(HandshakeWithRefs { outcome, refs }) } +} - /// List all references on the remote that have been filtered through our remote's [`refspecs`][crate::Remote::refspecs()] - /// for _fetching_ or _pushing_ depending on `direction`. - /// - /// This comes in the form of information of all matching tips on the remote and the object they point to, along with - /// with the local tracking branch of these tips (if available). - /// - /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. - pub fn list_refs_by_refspec(&mut self, _direction: Direction) -> ! { - todo!() +/// +pub mod to_mapping { + use crate::remote::{fetch, Connection}; + use git_features::progress::Progress; + use git_protocol::transport::client::Transport; + + /// The error returned by [`Connection::list_refs_to_mapping()`]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + ListRefs(#[from] crate::remote::list_refs::Error), + } + + impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> + where + T: Transport, + P: Progress, + { + /// List all references on the remote that have been filtered through our remote's [`refspecs`][crate::Remote::refspecs()] + /// for _fetching_. + /// + /// This comes in the form of all matching tips on the remote and the object they point to, along with + /// with the local tracking branch of these tips (if available). + /// + /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. + #[git_protocol::maybe_async::maybe_async] + pub async fn list_refs_to_mapping(mut self) -> Result, Error> { + let mappings = self.ref_mapping().await?; + git_protocol::fetch::indicate_end_of_interaction(&mut self.transport) + .await + .map_err(|err| Error::ListRefs(crate::remote::list_refs::Error::Transport(err)))?; + Ok(mappings) + } + + #[git_protocol::maybe_async::maybe_async] + async fn ref_mapping(&mut self) -> Result, Error> { + let _res = self.fetch_refs().await?; + let _group = git_refspec::MatchGroup::from_fetch_specs(self.remote.fetch_specs.iter().map(|s| s.to_ref())); + // group.match_remotes(res.refs.iter().map(|r| r.unpack())) + Ok(Vec::new()) + } } } diff --git a/git-repository/src/remote/connection/mod.rs b/git-repository/src/remote/connection/mod.rs index 8e3e4e449e0..20a0282a84b 100644 --- a/git-repository/src/remote/connection/mod.rs +++ b/git-repository/src/remote/connection/mod.rs @@ -6,6 +6,7 @@ pub(crate) struct HandshakeWithRefs { refs: Vec, } +/// A function that performs a given credential action. pub type CredentialsFn<'a> = Box git_credentials::protocol::Result + 'a>; /// A type to represent an ongoing connection to a remote host, typically with the connection already established. @@ -65,4 +66,5 @@ mod access { } } -mod list_refs; +/// +pub mod list_refs; diff --git a/git-repository/src/remote/mod.rs b/git-repository/src/remote/mod.rs index 265ad79a4d7..19bf3670d26 100644 --- a/git-repository/src/remote/mod.rs +++ b/git-repository/src/remote/mod.rs @@ -46,7 +46,7 @@ pub mod connect; #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] mod connection; #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] -pub use connection::Connection; +pub use connection::{list_refs, Connection}; mod access; pub(crate) mod url; diff --git a/git-repository/tests/remote/list_refs.rs b/git-repository/tests/remote/list_refs.rs index 01b85ca2fce..5f45a6aa0cc 100644 --- a/git-repository/tests/remote/list_refs.rs +++ b/git-repository/tests/remote/list_refs.rs @@ -22,10 +22,13 @@ mod blocking_io { (version as u8).to_string().as_str(), )?; } + let remote = repo.find_remote("origin")?; - let connection = remote.connect(Fetch, progress::Discard)?; - let refs = connection.list_refs()?; - assert_eq!(refs.len(), 14, "it gets all remote refs, independently of the refspec."); + { + let connection = remote.connect(Fetch, progress::Discard)?; + let refs = connection.list_refs()?; + assert_eq!(refs.len(), 14, "it gets all remote refs, independently of the refspec."); + } } Ok(()) } From 21420dacb485795e80baabf2a300ff900036ba7b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 09:41:14 +0800 Subject: [PATCH 03/32] fix `match_group::Item` to make it uniform with how we typically name refs (#450) --- git-refspec/src/match_group/mod.rs | 2 +- git-refspec/src/match_group/types.rs | 6 +++--- git-refspec/src/match_group/util.rs | 4 ++-- git-refspec/tests/matching/mod.rs | 13 +++++-------- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/git-refspec/src/match_group/mod.rs b/git-refspec/src/match_group/mod.rs index 5b10ee32cc4..cf9022a2fed 100644 --- a/git-refspec/src/match_group/mod.rs +++ b/git-refspec/src/match_group/mod.rs @@ -88,7 +88,7 @@ impl<'a> MatchGroup<'a> { .matches_lhs(Item { full_ref_name: name, target: &null_id, - tag: None, + object: None, }) .0 } diff --git a/git-refspec/src/match_group/types.rs b/git-refspec/src/match_group/types.rs index 61711252d9c..548d2de0786 100644 --- a/git-refspec/src/match_group/types.rs +++ b/git-refspec/src/match_group/types.rs @@ -25,10 +25,10 @@ pub struct Outcome<'spec, 'item> { pub struct Item<'a> { /// The full name of the references, like `refs/heads/main` pub full_ref_name: &'a BStr, - /// The peeled id it points to that we should match against. + /// The id that `full_ref_name` points to, which typically is a commit, but can also be a tag object (or anything else). pub target: &'a oid, - /// The tag object's id if this is a tag - pub tag: Option<&'a oid>, + /// The object an annotated tag is pointing to, if `target` is an annotated tag. + pub object: Option<&'a oid>, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] diff --git a/git-refspec/src/match_group/util.rs b/git-refspec/src/match_group/util.rs index 42a31c03acb..3aa73aaddfa 100644 --- a/git-refspec/src/match_group/util.rs +++ b/git-refspec/src/match_group/util.rs @@ -111,8 +111,8 @@ impl<'a> Needle<'a> { if *id == item.target { return Match::Normal; } - match item.tag { - Some(tag) if tag == *id => Match::Normal, + match item.object { + Some(object) if object == *id => Match::Normal, _ => Match::None, } } diff --git a/git-refspec/tests/matching/mod.rs b/git-refspec/tests/matching/mod.rs index bf88bb68d63..bc03b751523 100644 --- a/git-refspec/tests/matching/mod.rs +++ b/git-refspec/tests/matching/mod.rs @@ -18,8 +18,8 @@ pub mod baseline { pub struct Ref { pub name: BString, pub target: ObjectId, - /// Set if this is a tag, pointing to the tag object itself - pub tag: Option, + /// Set if `target` is an annotated tag, this being the object it points to. + pub object: Option, } impl Ref { @@ -27,7 +27,7 @@ pub mod baseline { git_refspec::match_group::Item { full_ref_name: self.name.borrow(), target: &self.target, - tag: self.tag.as_deref(), + object: self.object.as_deref(), } } } @@ -216,13 +216,10 @@ pub mod baseline { out.push(Ref { name: name.into(), target, - tag: None, + object: None, }) } else { - let last = out.last_mut().unwrap(); - let tag = last.target; - last.target = target; - last.tag = Some(tag); + out.last_mut().unwrap().object = Some(target); } } Ok(out) From bab586099a6e21e54932218f7a5c14f8bfc6cabd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 09:50:43 +0800 Subject: [PATCH 04/32] change!: rename `fetch::Ref::path` to `fetch::Ref::full_ref_name`. (#450) Previously it was inaccurately named and described due to an insufficient understanding of the ref database. --- git-protocol/src/fetch/refs/mod.rs | 32 +++++++++++++++++---------- git-protocol/src/fetch/refs/shared.rs | 30 ++++++++++++++++++------- git-protocol/src/fetch/tests/refs.rs | 20 ++++++++--------- git-protocol/tests/fetch/mod.rs | 4 ++-- git-protocol/tests/fetch/v1.rs | 4 ++-- git-protocol/tests/fetch/v2.rs | 6 ++--- 6 files changed, 59 insertions(+), 37 deletions(-) diff --git a/git-protocol/src/fetch/refs/mod.rs b/git-protocol/src/fetch/refs/mod.rs index a847d6107a4..829355484ae 100644 --- a/git-protocol/src/fetch/refs/mod.rs +++ b/git-protocol/src/fetch/refs/mod.rs @@ -50,8 +50,8 @@ pub mod parse { pub enum Ref { /// A ref pointing to a `tag` object, which in turns points to an `object`, usually a commit Peeled { - /// The path at which the ref is located, like `/refs/heads/main`. - path: BString, + /// The name at which the ref is located, like `refs/heads/main`. + full_ref_name: BString, /// The hash of the tag the ref points to. tag: git_hash::ObjectId, /// The hash of the object the `tag` points to. @@ -59,15 +59,15 @@ pub enum Ref { }, /// A ref pointing to a commit object Direct { - /// The path at which the ref is located, like `/refs/heads/main`. - path: BString, + /// The name at which the ref is located, like `refs/heads/main`. + full_ref_name: BString, /// The hash of the object the ref points to. object: git_hash::ObjectId, }, /// A symbolic ref pointing to `target` ref, which in turn points to an `object` Symbolic { - /// The path at which the symbolic ref is located, like `/refs/heads/main`. - path: BString, + /// The name at which the symbolic ref is located, like `refs/heads/main`. + full_ref_name: BString, /// The path of the ref the symbolic ref points to, see issue [#205] for details /// /// [#205]: https://github.com/Byron/gitoxide/issues/205 @@ -78,13 +78,21 @@ pub enum Ref { } impl Ref { - /// Provide shared fields referring to the ref itself, namely `(path, object id)`. - /// In case of peeled refs, the tag object itself is returned as it is what the path refers to. - pub fn unpack(&self) -> (&BString, &git_hash::ObjectId) { + /// Provide shared fields referring to the ref itself, namely `(name, target)`. + /// In case of peeled refs, the tag object itself is returned as it is what the ref directly refers to. + pub fn unpack(&self) -> (&BString, &git_hash::oid) { match self { - Ref::Direct { path, object, .. } - | Ref::Peeled { path, tag: object, .. } // the tag acts as reference - | Ref::Symbolic { path, object, .. } => (path, object), + Ref::Direct { + full_ref_name, object, .. + } + | Ref::Peeled { + full_ref_name, + tag: object, + .. + } + | Ref::Symbolic { + full_ref_name, object, .. + } => (full_ref_name, object), } } } diff --git a/git-protocol/src/fetch/refs/shared.rs b/git-protocol/src/fetch/refs/shared.rs index 46a8ad53620..87e0541d49a 100644 --- a/git-protocol/src/fetch/refs/shared.rs +++ b/git-protocol/src/fetch/refs/shared.rs @@ -9,14 +9,28 @@ impl From for Ref { path, target: Some(target), object, - } => Ref::Symbolic { path, target, object }, + } => Ref::Symbolic { + full_ref_name: path, + target, + object, + }, InternalRef::Symbolic { path, target: None, object, - } => Ref::Direct { path, object }, - InternalRef::Peeled { path, tag, object } => Ref::Peeled { path, tag, object }, - InternalRef::Direct { path, object } => Ref::Direct { path, object }, + } => Ref::Direct { + full_ref_name: path, + object, + }, + InternalRef::Peeled { path, tag, object } => Ref::Peeled { + full_ref_name: path, + tag, + object, + }, + InternalRef::Direct { path, object } => Ref::Direct { + full_ref_name: path, + object, + }, InternalRef::SymbolicForLookup { .. } => { unreachable!("this case should have been removed during processing") } @@ -170,17 +184,17 @@ pub(in crate::fetch::refs) fn parse_v2(line: &str) -> Result { } match attribute { "peeled" => Ref::Peeled { - path: path.into(), + full_ref_name: path.into(), object: git_hash::ObjectId::from_hex(value.as_bytes())?, tag: id, }, "symref-target" => match value { "(null)" => Ref::Direct { - path: path.into(), + full_ref_name: path.into(), object: id, }, name => Ref::Symbolic { - path: path.into(), + full_ref_name: path.into(), object: id, target: name.into(), }, @@ -198,7 +212,7 @@ pub(in crate::fetch::refs) fn parse_v2(line: &str) -> Result { } else { Ref::Direct { object: id, - path: path.into(), + full_ref_name: path.into(), } }) } diff --git a/git-protocol/src/fetch/tests/refs.rs b/git-protocol/src/fetch/tests/refs.rs index a07d75f325e..e6ca670ba59 100644 --- a/git-protocol/src/fetch/tests/refs.rs +++ b/git-protocol/src/fetch/tests/refs.rs @@ -19,25 +19,25 @@ async fn extract_references_from_v2_refs() { out, vec![ Ref::Symbolic { - path: "HEAD".into(), + full_ref_name: "HEAD".into(), target: "refs/heads/main".into(), object: oid("808e50d724f604f69ab93c6da2919c014667bedb") }, Ref::Direct { - path: "MISSING_NAMESPACE_TARGET".into(), + full_ref_name: "MISSING_NAMESPACE_TARGET".into(), object: oid("808e50d724f604f69ab93c6da2919c014667bedb") }, Ref::Direct { - path: "refs/heads/main".into(), + full_ref_name: "refs/heads/main".into(), object: oid("808e50d724f604f69ab93c6da2919c014667bedb") }, Ref::Peeled { - path: "refs/tags/foo".into(), + full_ref_name: "refs/tags/foo".into(), tag: oid("7fe1b98b39423b71e14217aa299a03b7c937d656"), object: oid("808e50d724f604f69ab93c6da2919c014667bedb") }, Ref::Direct { - path: "refs/tags/blaz".into(), + full_ref_name: "refs/tags/blaz".into(), object: oid("7fe1b98b39423b71e14217aa299a03b7c937d6ff") }, ] @@ -66,24 +66,24 @@ dce0ea858eef7ff61ad345cc5cdac62203fb3c10 refs/tags/git-commitgraph-v0.0.0 out, vec![ Ref::Symbolic { - path: "HEAD".into(), + full_ref_name: "HEAD".into(), target: "refs/heads/main".into(), object: oid("73a6868963993a3328e7d8fe94e5a6ac5078a944") }, Ref::Direct { - path: "MISSING_NAMESPACE_TARGET".into(), + full_ref_name: "MISSING_NAMESPACE_TARGET".into(), object: oid("21c9b7500cb144b3169a6537961ec2b9e865be81") }, Ref::Direct { - path: "refs/heads/main".into(), + full_ref_name: "refs/heads/main".into(), object: oid("73a6868963993a3328e7d8fe94e5a6ac5078a944") }, Ref::Direct { - path: "refs/pull/13/head".into(), + full_ref_name: "refs/pull/13/head".into(), object: oid("8e472f9ccc7d745927426cbb2d9d077de545aa4e") }, Ref::Peeled { - path: "refs/tags/git-commitgraph-v0.0.0".into(), + full_ref_name: "refs/tags/git-commitgraph-v0.0.0".into(), tag: oid("dce0ea858eef7ff61ad345cc5cdac62203fb3c10"), object: oid("21c9b7500cb144b3169a6537961ec2b9e865be81") }, diff --git a/git-protocol/tests/fetch/mod.rs b/git-protocol/tests/fetch/mod.rs index c5ff0e0da09..dfdcdbdf309 100644 --- a/git-protocol/tests/fetch/mod.rs +++ b/git-protocol/tests/fetch/mod.rs @@ -169,7 +169,7 @@ mod blocking_io { ) -> io::Result<()> { for wanted in response.wanted_refs() { self.wanted_refs.push(fetch::Ref::Direct { - path: wanted.path.clone(), + full_ref_name: wanted.path.clone(), object: wanted.id, }); } @@ -230,7 +230,7 @@ mod async_io { ) -> io::Result<()> { for wanted in response.wanted_refs() { self.wanted_refs.push(fetch::Ref::Direct { - path: wanted.path.clone(), + full_ref_name: wanted.path.clone(), object: wanted.id, }); } diff --git a/git-protocol/tests/fetch/v1.rs b/git-protocol/tests/fetch/v1.rs index 9088b9d3c99..2820ebab364 100644 --- a/git-protocol/tests/fetch/v1.rs +++ b/git-protocol/tests/fetch/v1.rs @@ -49,12 +49,12 @@ async fn ls_remote() -> crate::Result { delegate.refs, vec![ fetch::Ref::Symbolic { - path: "HEAD".into(), + full_ref_name: "HEAD".into(), object: oid("808e50d724f604f69ab93c6da2919c014667bedb"), target: "refs/heads/master".into() }, fetch::Ref::Direct { - path: "refs/heads/master".into(), + full_ref_name: "refs/heads/master".into(), object: oid("808e50d724f604f69ab93c6da2919c014667bedb") } ] diff --git a/git-protocol/tests/fetch/v2.rs b/git-protocol/tests/fetch/v2.rs index d4bf585447a..ba3bb1ca820 100644 --- a/git-protocol/tests/fetch/v2.rs +++ b/git-protocol/tests/fetch/v2.rs @@ -75,12 +75,12 @@ async fn ls_remote() -> crate::Result { delegate.refs, vec![ fetch::Ref::Symbolic { - path: "HEAD".into(), + full_ref_name: "HEAD".into(), object: oid("808e50d724f604f69ab93c6da2919c014667bedb"), target: "refs/heads/master".into() }, fetch::Ref::Direct { - path: "refs/heads/master".into(), + full_ref_name: "refs/heads/master".into(), object: oid("808e50d724f604f69ab93c6da2919c014667bedb") } ] @@ -167,7 +167,7 @@ async fn ref_in_want() -> crate::Result { assert_eq!( delegate.wanted_refs, vec![fetch::Ref::Direct { - path: "refs/heads/main".into(), + full_ref_name: "refs/heads/main".into(), object: oid("9e320b9180e0b5580af68fa3255b7f3d9ecd5af0"), }] ); From d2c772e28619f7602ab01285b25e79a7040c9aab Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 10:01:08 +0800 Subject: [PATCH 05/32] change!: `fetch::Ref::unpack()` now returns the peeled object as well. (#450) --- git-protocol/src/fetch/refs/mod.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/git-protocol/src/fetch/refs/mod.rs b/git-protocol/src/fetch/refs/mod.rs index 829355484ae..1b3eba26433 100644 --- a/git-protocol/src/fetch/refs/mod.rs +++ b/git-protocol/src/fetch/refs/mod.rs @@ -78,21 +78,20 @@ pub enum Ref { } impl Ref { - /// Provide shared fields referring to the ref itself, namely `(name, target)`. - /// In case of peeled refs, the tag object itself is returned as it is what the ref directly refers to. - pub fn unpack(&self) -> (&BString, &git_hash::oid) { + /// Provide shared fields referring to the ref itself, namely `(name, target, [peeled])`. + /// In case of peeled refs, the tag object itself is returned as it is what the ref directly refers to, and target of the tag is returned + /// as `peeled`. + pub fn unpack(&self) -> (&BString, &git_hash::oid, Option<&git_hash::oid>) { match self { - Ref::Direct { + Ref::Direct { full_ref_name, object } + | Ref::Symbolic { full_ref_name, object, .. - } - | Ref::Peeled { + } => (full_ref_name, object, None), + Ref::Peeled { full_ref_name, tag: object, - .. - } - | Ref::Symbolic { - full_ref_name, object, .. - } => (full_ref_name, object), + object: peeled, + } => (full_ref_name, object, Some(peeled)), } } } From 06d45ff10ec2c401ac52d77f7430a8c8c0ba93a6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 10:02:43 +0800 Subject: [PATCH 06/32] adjust to changes in `git-protocol` (#450) --- gitoxide-core/src/pack/receive.rs | 18 +++++++++++--- gitoxide-core/src/repository/remote.rs | 34 +++++++++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/gitoxide-core/src/pack/receive.rs b/gitoxide-core/src/pack/receive.rs index 784eef5cf8b..673196d48ee 100644 --- a/gitoxide-core/src/pack/receive.rs +++ b/gitoxide-core/src/pack/receive.rs @@ -90,7 +90,7 @@ impl protocol::fetch::DelegateBlocking for CloneDelegate { ) -> io::Result { if self.wanted_refs.is_empty() { for r in refs { - let (path, id) = r.unpack(); + let (path, id, _) = r.unpack(); match self.ref_filter { Some(ref_prefixes) => { if ref_prefixes.iter().any(|prefix| path.starts_with_str(prefix)) { @@ -310,10 +310,20 @@ fn write_raw_refs(refs: &[Ref], directory: PathBuf) -> std::io::Result<()> { }; for r in refs { let (path, content) = match r { - Ref::Symbolic { path, target, .. } => (assure_dir_exists(path)?, format!("ref: {}", target)), - Ref::Peeled { path, tag: object, .. } | Ref::Direct { path, object } => { - (assure_dir_exists(path)?, object.to_string()) + Ref::Symbolic { + full_ref_name: path, + target, + .. + } => (assure_dir_exists(path)?, format!("ref: {}", target)), + Ref::Peeled { + full_ref_name: path, + tag: object, + .. } + | Ref::Direct { + full_ref_name: path, + object, + } => (assure_dir_exists(path)?, object.to_string()), }; std::fs::write(path, content.as_bytes())?; } diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index 5b7ed841376..e7efce58e11 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -81,16 +81,27 @@ mod net { impl From for JsonRef { fn from(value: fetch::Ref) -> Self { match value { - fetch::Ref::Direct { path, object } => JsonRef::Direct { + fetch::Ref::Direct { + full_ref_name: path, + object, + } => JsonRef::Direct { path: path.to_string(), object: object.to_string(), }, - fetch::Ref::Symbolic { path, target, object } => JsonRef::Symbolic { + fetch::Ref::Symbolic { + full_ref_name: path, + target, + object, + } => JsonRef::Symbolic { path: path.to_string(), target: target.to_string(), object: object.to_string(), }, - fetch::Ref::Peeled { path, tag, object } => JsonRef::Peeled { + fetch::Ref::Peeled { + full_ref_name: path, + tag, + object, + } => JsonRef::Peeled { path: path.to_string(), tag: tag.to_string(), object: object.to_string(), @@ -102,11 +113,22 @@ mod net { pub(crate) fn print(mut out: impl std::io::Write, refs: &[fetch::Ref]) -> std::io::Result<()> { for r in refs { match r { - fetch::Ref::Direct { path, object } => writeln!(&mut out, "{} {}", object.to_hex(), path), - fetch::Ref::Peeled { path, object, tag } => { + fetch::Ref::Direct { + full_ref_name: path, + object, + } => writeln!(&mut out, "{} {}", object.to_hex(), path), + fetch::Ref::Peeled { + full_ref_name: path, + object, + tag, + } => { writeln!(&mut out, "{} {} tag:{}", object.to_hex(), path, tag) } - fetch::Ref::Symbolic { path, target, object } => { + fetch::Ref::Symbolic { + full_ref_name: path, + target, + object, + } => { writeln!(&mut out, "{} {} symref-target:{}", object.to_hex(), path, target) } }?; From 91f193f73ae37314319f7d055893b95431fd018e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 10:05:00 +0800 Subject: [PATCH 07/32] change!: `fetch::Ref::unpack()` returns `&BStr` instead of `&BString` as ref name. (#450) --- git-protocol/src/fetch/refs/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-protocol/src/fetch/refs/mod.rs b/git-protocol/src/fetch/refs/mod.rs index 1b3eba26433..ba3fafd2471 100644 --- a/git-protocol/src/fetch/refs/mod.rs +++ b/git-protocol/src/fetch/refs/mod.rs @@ -1,4 +1,4 @@ -use bstr::BString; +use bstr::{BStr, BString}; mod error { use crate::fetch::refs::parse; @@ -81,17 +81,17 @@ impl Ref { /// Provide shared fields referring to the ref itself, namely `(name, target, [peeled])`. /// In case of peeled refs, the tag object itself is returned as it is what the ref directly refers to, and target of the tag is returned /// as `peeled`. - pub fn unpack(&self) -> (&BString, &git_hash::oid, Option<&git_hash::oid>) { + pub fn unpack(&self) -> (&BStr, &git_hash::oid, Option<&git_hash::oid>) { match self { Ref::Direct { full_ref_name, object } | Ref::Symbolic { full_ref_name, object, .. - } => (full_ref_name, object, None), + } => (full_ref_name.as_ref(), object, None), Ref::Peeled { full_ref_name, tag: object, object: peeled, - } => (full_ref_name, object, Some(peeled)), + } => (full_ref_name.as_ref(), object, Some(peeled)), } } } From 777ba7ffe4b20e2cdf4069eeb504df432db58d69 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 10:13:27 +0800 Subject: [PATCH 08/32] A step closer to actually obtaining a validated ref-mapping. (#450) Now the types have to be converted to refer to our remote's refspecs, possibly, even though maybe that's too much and a simple 1:1 conversion that degenerates information is enough for all the use-cases. --- .../src/remote/connection/list_refs.rs | 30 ++++++++++++++----- git-repository/src/remote/mod.rs | 8 +++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/git-repository/src/remote/connection/list_refs.rs b/git-repository/src/remote/connection/list_refs.rs index 9d96eac0f19..42e01c0c5eb 100644 --- a/git-repository/src/remote/connection/list_refs.rs +++ b/git-repository/src/remote/connection/list_refs.rs @@ -70,7 +70,7 @@ where } /// -pub mod to_mapping { +pub mod to_map { use crate::remote::{fetch, Connection}; use git_features::progress::Progress; use git_protocol::transport::client::Transport; @@ -81,6 +81,8 @@ pub mod to_mapping { pub enum Error { #[error(transparent)] ListRefs(#[from] crate::remote::list_refs::Error), + #[error(transparent)] + MappingValidation(#[from] git_refspec::match_group::validate::Error), } impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> @@ -96,8 +98,8 @@ pub mod to_mapping { /// /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. #[git_protocol::maybe_async::maybe_async] - pub async fn list_refs_to_mapping(mut self) -> Result, Error> { - let mappings = self.ref_mapping().await?; + pub async fn list_refs_to_map(mut self) -> Result { + let mappings = self.ref_map().await?; git_protocol::fetch::indicate_end_of_interaction(&mut self.transport) .await .map_err(|err| Error::ListRefs(crate::remote::list_refs::Error::Transport(err)))?; @@ -105,11 +107,23 @@ pub mod to_mapping { } #[git_protocol::maybe_async::maybe_async] - async fn ref_mapping(&mut self) -> Result, Error> { - let _res = self.fetch_refs().await?; - let _group = git_refspec::MatchGroup::from_fetch_specs(self.remote.fetch_specs.iter().map(|s| s.to_ref())); - // group.match_remotes(res.refs.iter().map(|r| r.unpack())) - Ok(Vec::new()) + async fn ref_map(&mut self) -> Result { + let res = self.fetch_refs().await?; + let group = git_refspec::MatchGroup::from_fetch_specs(self.remote.fetch_specs.iter().map(|s| s.to_ref())); + let (_res, fixes) = group + .match_remotes(res.refs.iter().map(|r| { + let (full_ref_name, target, object) = r.unpack(); + git_refspec::match_group::Item { + full_ref_name, + target, + object, + } + })) + .validated()?; + Ok(fetch::RefMap { + mappings: Vec::new(), + fixes, + }) } } } diff --git a/git-repository/src/remote/mod.rs b/git-repository/src/remote/mod.rs index 19bf3670d26..c40ad19ced5 100644 --- a/git-repository/src/remote/mod.rs +++ b/git-repository/src/remote/mod.rs @@ -30,6 +30,14 @@ pub mod init; pub mod fetch { use crate::bstr::BString; + /// Information about the relationship between our refspecs, and remote references with their local counterparts. + pub struct RefMap { + /// A mapping between a remote reference and a local tracking branch. + pub mappings: Vec, + /// Information about the fixes applied to the `mapping` due to validation and sanitization. + pub fixes: Vec, + } + /// A mapping between a single remote reference and its advertised objects to a local destination which may or may not exist. pub struct Mapping { /// The reference on the remote side, along with information about the objects they point to as advertised by the server. From 7e96d1841989b37133cddf334724a2d6df557e69 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 11:14:04 +0800 Subject: [PATCH 09/32] feat: obtain a refmap after listing refs via `remote::Connection::list_refs_to_map()`. (#450) With it it's possible to establish a relationship between what's about to be fetched to local tracking branches as established by refspecs for fetching. --- .../src/remote/connection/list_refs.rs | 35 +++++++++++++------ git-repository/src/remote/mod.rs | 10 +++++- git-repository/tests/remote/list_refs.rs | 11 ++++++ 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/git-repository/src/remote/connection/list_refs.rs b/git-repository/src/remote/connection/list_refs.rs index 42e01c0c5eb..aa1ae6310e0 100644 --- a/git-repository/src/remote/connection/list_refs.rs +++ b/git-repository/src/remote/connection/list_refs.rs @@ -27,9 +27,9 @@ where /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. #[git_protocol::maybe_async::maybe_async] pub async fn list_refs(mut self) -> Result, Error> { - let res = self.fetch_refs().await?; + let res = self.fetch_refs().await; git_protocol::fetch::indicate_end_of_interaction(&mut self.transport).await?; - Ok(res.refs) + Ok(res?.refs) } #[git_protocol::maybe_async::maybe_async] @@ -99,19 +99,19 @@ pub mod to_map { /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. #[git_protocol::maybe_async::maybe_async] pub async fn list_refs_to_map(mut self) -> Result { - let mappings = self.ref_map().await?; + let res = self.ref_map().await; git_protocol::fetch::indicate_end_of_interaction(&mut self.transport) .await .map_err(|err| Error::ListRefs(crate::remote::list_refs::Error::Transport(err)))?; - Ok(mappings) + Ok(res?) } #[git_protocol::maybe_async::maybe_async] async fn ref_map(&mut self) -> Result { - let res = self.fetch_refs().await?; + let remote = self.fetch_refs().await?; let group = git_refspec::MatchGroup::from_fetch_specs(self.remote.fetch_specs.iter().map(|s| s.to_ref())); - let (_res, fixes) = group - .match_remotes(res.refs.iter().map(|r| { + let (res, fixes) = group + .match_remotes(remote.refs.iter().map(|r| { let (full_ref_name, target, object) = r.unpack(); git_refspec::match_group::Item { full_ref_name, @@ -120,10 +120,23 @@ pub mod to_map { } })) .validated()?; - Ok(fetch::RefMap { - mappings: Vec::new(), - fixes, - }) + let mappings = res.mappings; + let mappings = mappings + .into_iter() + .map(|m| fetch::Mapping { + remote: m + .item_index + .map(|idx| fetch::Source::Ref(remote.refs[idx].clone())) + .unwrap_or_else(|| { + fetch::Source::ObjectId(match m.lhs { + git_refspec::match_group::SourceRef::ObjectId(id) => id, + _ => unreachable!("no item index implies having an object id"), + }) + }), + local: m.rhs.map(|c| c.into_owned()), + }) + .collect(); + Ok(fetch::RefMap { mappings, fixes }) } } } diff --git a/git-repository/src/remote/mod.rs b/git-repository/src/remote/mod.rs index c40ad19ced5..72b4015bf20 100644 --- a/git-repository/src/remote/mod.rs +++ b/git-repository/src/remote/mod.rs @@ -38,10 +38,18 @@ pub mod fetch { pub fixes: Vec, } + /// Either an object id that the remote has or the matched remote ref itself. + pub enum Source { + /// An object id, as the matched ref-spec was an object id itself. + ObjectId(git_hash::ObjectId), + /// The remote reference that matched the ref-specs name. + Ref(git_protocol::fetch::Ref), + } + /// A mapping between a single remote reference and its advertised objects to a local destination which may or may not exist. pub struct Mapping { /// The reference on the remote side, along with information about the objects they point to as advertised by the server. - pub remote: git_protocol::fetch::Ref, + pub remote: Source, /// The local tracking reference to update after fetching the object visible via `remote`. pub local: Option, } diff --git a/git-repository/tests/remote/list_refs.rs b/git-repository/tests/remote/list_refs.rs index 5f45a6aa0cc..0a441998ce5 100644 --- a/git-repository/tests/remote/list_refs.rs +++ b/git-repository/tests/remote/list_refs.rs @@ -29,6 +29,17 @@ mod blocking_io { let refs = connection.list_refs()?; assert_eq!(refs.len(), 14, "it gets all remote refs, independently of the refspec."); } + + { + let connection = remote.connect(Fetch, progress::Discard)?; + let map = connection.list_refs_to_map()?; + assert_eq!(map.fixes.len(), 0); + assert_eq!( + map.mappings.len(), + 11, + "mappings are only a sub-set of all remotes due to refspec matching" + ); + } } Ok(()) } From 52fa247d3ba211ccacfc867d2f0a17022e2b9b62 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 11:16:57 +0800 Subject: [PATCH 10/32] thanks clippy --- git-repository/src/remote/connection/list_refs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-repository/src/remote/connection/list_refs.rs b/git-repository/src/remote/connection/list_refs.rs index aa1ae6310e0..2b4077ba0b3 100644 --- a/git-repository/src/remote/connection/list_refs.rs +++ b/git-repository/src/remote/connection/list_refs.rs @@ -103,7 +103,7 @@ pub mod to_map { git_protocol::fetch::indicate_end_of_interaction(&mut self.transport) .await .map_err(|err| Error::ListRefs(crate::remote::list_refs::Error::Transport(err)))?; - Ok(res?) + res } #[git_protocol::maybe_async::maybe_async] From 85c49ec16ac7eeb2175fa43545c72b81da693ab1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 11:20:06 +0800 Subject: [PATCH 11/32] Allow `match_group::Fix` to be cloned. (#450) --- git-refspec/src/match_group/validate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-refspec/src/match_group/validate.rs b/git-refspec/src/match_group/validate.rs index 0443a47b3cf..c1f30ce8ae8 100644 --- a/git-refspec/src/match_group/validate.rs +++ b/git-refspec/src/match_group/validate.rs @@ -43,7 +43,7 @@ impl std::fmt::Display for Issue { } /// All possible fixes corrected while validating matched mappings. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum Fix { /// Removed a mapping that contained a partial destination entirely. MappingWithPartialDestinationRemoved { From ffa24a1365480523197b5247bded6a7a4772bdfc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 11:21:36 +0800 Subject: [PATCH 12/32] adjust to changes in `git-refspec` (#450) --- git-protocol/src/fetch/handshake.rs | 1 + git-repository/src/remote/connection/list_refs.rs | 2 +- git-repository/src/remote/mod.rs | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/git-protocol/src/fetch/handshake.rs b/git-protocol/src/fetch/handshake.rs index 292094cfa48..895c6ac0366 100644 --- a/git-protocol/src/fetch/handshake.rs +++ b/git-protocol/src/fetch/handshake.rs @@ -3,6 +3,7 @@ use git_transport::client::Capabilities; use crate::fetch::Ref; /// The result of the [`handshake()`][super::handshake()] function. +#[derive(Debug, Clone)] pub struct Outcome { /// The protocol version the server responded with. It might have downgraded the desired version. pub server_protocol_version: git_transport::Protocol, diff --git a/git-repository/src/remote/connection/list_refs.rs b/git-repository/src/remote/connection/list_refs.rs index 2b4077ba0b3..3d12a48c358 100644 --- a/git-repository/src/remote/connection/list_refs.rs +++ b/git-repository/src/remote/connection/list_refs.rs @@ -75,7 +75,7 @@ pub mod to_map { use git_features::progress::Progress; use git_protocol::transport::client::Transport; - /// The error returned by [`Connection::list_refs_to_mapping()`]. + /// The error returned by [`Connection::list_refs_to_map()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/git-repository/src/remote/mod.rs b/git-repository/src/remote/mod.rs index 72b4015bf20..f35782d0da3 100644 --- a/git-repository/src/remote/mod.rs +++ b/git-repository/src/remote/mod.rs @@ -31,6 +31,7 @@ pub mod fetch { use crate::bstr::BString; /// Information about the relationship between our refspecs, and remote references with their local counterparts. + #[derive(Debug, Clone)] pub struct RefMap { /// A mapping between a remote reference and a local tracking branch. pub mappings: Vec, @@ -39,6 +40,7 @@ pub mod fetch { } /// Either an object id that the remote has or the matched remote ref itself. + #[derive(Debug, Clone)] pub enum Source { /// An object id, as the matched ref-spec was an object id itself. ObjectId(git_hash::ObjectId), @@ -47,6 +49,7 @@ pub mod fetch { } /// A mapping between a single remote reference and its advertised objects to a local destination which may or may not exist. + #[derive(Debug, Clone)] pub struct Mapping { /// The reference on the remote side, along with information about the objects they point to as advertised by the server. pub remote: Source, From 3958d71177927305399369088d184891c182d9e2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 14:36:49 +0800 Subject: [PATCH 13/32] Provide all information generated by the handshake into the refmap result (#450) --- git-repository/src/config/cache/incubate.rs | 1 - git-repository/src/open.rs | 1 - git-repository/src/remote/connection/list_refs.rs | 7 ++++++- git-repository/src/remote/connection/mod.rs | 1 - git-repository/src/remote/mod.rs | 6 ++++++ git-repository/src/remote/url/scheme_permission.rs | 2 -- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/git-repository/src/config/cache/incubate.rs b/git-repository/src/config/cache/incubate.rs index 24ff91bcd88..f8048a535ab 100644 --- a/git-repository/src/config/cache/incubate.rs +++ b/git-repository/src/config/cache/incubate.rs @@ -2,7 +2,6 @@ use super::{util, Error}; /// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the /// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. -#[allow(dead_code)] pub(crate) struct StageOne { pub git_dir_config: git_config::File<'static>, pub buf: Vec, diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index a3c7048082c..6ee8b0dd4ae 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -90,7 +90,6 @@ impl Default for Options { } #[derive(Default, Clone)] -#[allow(dead_code)] pub(crate) struct EnvironmentOverrides { /// An override of the worktree typically from the environment, and overrides even worktree dirs set as parameter. /// diff --git a/git-repository/src/remote/connection/list_refs.rs b/git-repository/src/remote/connection/list_refs.rs index 3d12a48c358..87c1b23f08e 100644 --- a/git-repository/src/remote/connection/list_refs.rs +++ b/git-repository/src/remote/connection/list_refs.rs @@ -136,7 +136,12 @@ pub mod to_map { local: m.rhs.map(|c| c.into_owned()), }) .collect(); - Ok(fetch::RefMap { mappings, fixes }) + Ok(fetch::RefMap { + mappings, + fixes, + remote_refs: remote.refs, + handshake: remote.outcome, + }) } } } diff --git a/git-repository/src/remote/connection/mod.rs b/git-repository/src/remote/connection/mod.rs index 20a0282a84b..09b8342d595 100644 --- a/git-repository/src/remote/connection/mod.rs +++ b/git-repository/src/remote/connection/mod.rs @@ -1,7 +1,6 @@ use crate::Remote; pub(crate) struct HandshakeWithRefs { - #[allow(dead_code)] outcome: git_protocol::fetch::handshake::Outcome, refs: Vec, } diff --git a/git-repository/src/remote/mod.rs b/git-repository/src/remote/mod.rs index f35782d0da3..ed9d5a82393 100644 --- a/git-repository/src/remote/mod.rs +++ b/git-repository/src/remote/mod.rs @@ -37,6 +37,12 @@ pub mod fetch { pub mappings: Vec, /// Information about the fixes applied to the `mapping` due to validation and sanitization. pub fixes: Vec, + /// All refs advertised by the remote. + pub remote_refs: Vec, + /// Additional information provided by the server as part of the handshake. + /// + /// Note that the `refs` field is always `None` as the refs are placed in `remote_refs`. + pub handshake: git_protocol::fetch::handshake::Outcome, } /// Either an object id that the remote has or the matched remote ref itself. diff --git a/git-repository/src/remote/url/scheme_permission.rs b/git-repository/src/remote/url/scheme_permission.rs index 4493d175dbf..db8d7aeefba 100644 --- a/git-repository/src/remote/url/scheme_permission.rs +++ b/git-repository/src/remote/url/scheme_permission.rs @@ -1,5 +1,3 @@ -#![allow(dead_code, unused_variables)] - use std::{borrow::Cow, collections::BTreeMap, convert::TryFrom}; use crate::bstr::{BStr, BString, ByteSlice}; From 08fd1e4ad100d97db8ef0670894b56506759f83c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 14:53:01 +0800 Subject: [PATCH 14/32] list `git-refspec` crate as well (#450) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d064cc3d831..7fdab138ec6 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ is usable to some extend. * [git-revision](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-revision) * [git-command](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-command) * [git-prompt](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-prompt) + * [git-refspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-refspec) * `gitoxide-core` * **very early** _(possibly without any documentation and many rough edges)_ * [git-worktree](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-worktree) From 2a7df323b15678aee3e61a41908aceb644873b11 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 14:54:01 +0800 Subject: [PATCH 15/32] Make `specs` in `MatchGroup` public to reduce API surface. (#450) This also puts the burden of accessing specs and keeping these values consistent to the user. --- git-refspec/src/match_group/mod.rs | 5 ----- git-refspec/src/match_group/types.rs | 7 ++++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/git-refspec/src/match_group/mod.rs b/git-refspec/src/match_group/mod.rs index cf9022a2fed..b8060bc74ef 100644 --- a/git-refspec/src/match_group/mod.rs +++ b/git-refspec/src/match_group/mod.rs @@ -100,11 +100,6 @@ impl<'a> MatchGroup<'a> { mappings: out, } } - - /// Return the spec that produced the given `mapping`. - pub fn spec_by_mapping(&self, mapping: &Mapping<'_, '_>) -> RefSpecRef<'a> { - self.specs[mapping.spec_index] - } } fn calculate_hash(t: &T) -> u64 { diff --git a/git-refspec/src/match_group/types.rs b/git-refspec/src/match_group/types.rs index 548d2de0786..c664adc840d 100644 --- a/git-refspec/src/match_group/types.rs +++ b/git-refspec/src/match_group/types.rs @@ -4,9 +4,10 @@ use git_hash::oid; use std::borrow::Cow; /// A match group is able to match a list of ref specs in order while handling negation, conflicts and one to many mappings. -#[derive(Debug, Clone)] +#[derive(Default, Debug, Clone)] pub struct MatchGroup<'a> { - pub(crate) specs: Vec>, + /// The specs that take part in item matching. + pub specs: Vec>, } /// The outcome of any matching operation of a [`MatchGroup`]. @@ -90,7 +91,7 @@ pub struct Mapping<'a, 'b> { /// The name of the local side for fetches or the remote one for pushes that corresponds to `lhs`, if available. pub rhs: Option>, /// The index of the matched ref-spec as seen from the match group. - pub(crate) spec_index: usize, + pub spec_index: usize, } impl std::hash::Hash for Mapping<'_, '_> { From 910cedc09212e0d08c4a03db959974bc0810fa9d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Sep 2022 14:56:35 +0800 Subject: [PATCH 16/32] also provide the spec-index with the returned refmap. (#450) This allows for interesting visualizations, and maybe make clear how ref-specs work to users by showing them with what they matched. --- git-repository/src/remote/connection/list_refs.rs | 1 + git-repository/src/remote/mod.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/git-repository/src/remote/connection/list_refs.rs b/git-repository/src/remote/connection/list_refs.rs index 87c1b23f08e..660eaa4fd4a 100644 --- a/git-repository/src/remote/connection/list_refs.rs +++ b/git-repository/src/remote/connection/list_refs.rs @@ -134,6 +134,7 @@ pub mod to_map { }) }), local: m.rhs.map(|c| c.into_owned()), + spec_index: m.spec_index, }) .collect(); Ok(fetch::RefMap { diff --git a/git-repository/src/remote/mod.rs b/git-repository/src/remote/mod.rs index ed9d5a82393..ee1351f2f96 100644 --- a/git-repository/src/remote/mod.rs +++ b/git-repository/src/remote/mod.rs @@ -61,6 +61,8 @@ pub mod fetch { pub remote: Source, /// The local tracking reference to update after fetching the object visible via `remote`. pub local: Option, + /// The index into the fetch ref-specs used to produce the mapping, allowing it to be recovered. + pub spec_index: usize, } } From 27fb1ce27d2985eb1ee8bee5fffaf759902571fb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 10:11:00 +0800 Subject: [PATCH 17/32] change!: Add `Kind::GitInstallation` for a way to obtain special git-installation configuration paths. (#450) Note that these are lazily cached as they call the `git` binary. --- Cargo.lock | 1 + git-config/Cargo.toml | 1 + git-config/src/source.rs | 63 +++++++++++++++++++++++++++++++++++++++- git-config/src/types.rs | 2 ++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 5dd5cefad16..cf92d12399e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,6 +1188,7 @@ dependencies = [ "git-testtools", "memchr", "nom", + "once_cell", "serde", "serde_derive", "serial_test 0.7.0", diff --git a/git-config/Cargo.toml b/git-config/Cargo.toml index a5afbd107bf..685503dd7ec 100644 --- a/git-config/Cargo.toml +++ b/git-config/Cargo.toml @@ -29,6 +29,7 @@ unicode-bom = "1.1.4" bstr = { version = "1.0.1", default-features = false, features = ["std"] } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} smallvec = "1.9.0" +once_cell = "1.14.0" document-features = { version = "0.2.0", optional = true } diff --git a/git-config/src/source.rs b/git-config/src/source.rs index 1943e455679..7a21dc04a0a 100644 --- a/git-config/src/source.rs +++ b/git-config/src/source.rs @@ -9,6 +9,8 @@ use crate::Source; /// The category of a [`Source`], in order of ascending precedence. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Kind { + /// A special configuration file that ships with the git installation, and is thus tied to the used git binary. + GitInstallation, /// A source shared for the entire system. System, /// Application specific configuration unique for each user of the `System`. @@ -23,7 +25,8 @@ impl Kind { /// Return a list of sources associated with this `Kind` of source, in order of ascending precedence. pub fn sources(self) -> &'static [Source] { let src = match self { - Kind::System => &[Source::System] as &[_], + Kind::GitInstallation => &[Source::GitInstallation] as &[_], + Kind::System => &[Source::System], Kind::Global => &[Source::Git, Source::User], Kind::Repository => &[Source::Local, Source::Worktree], Kind::Override => &[Source::Env, Source::Cli, Source::Api], @@ -41,6 +44,7 @@ impl Source { pub const fn kind(self) -> Kind { use Source::*; match self { + GitInstallation => Kind::GitInstallation, System => Kind::System, Git | User => Kind::Global, Local | Worktree => Kind::Repository, @@ -61,6 +65,7 @@ impl Source { pub fn storage_location(self, env_var: &mut dyn FnMut(&str) -> Option) -> Option> { use Source::*; match self { + GitInstallation => git::install_config_path().map(git_path::from_bstr), System => env_var("GIT_CONFIG_NO_SYSTEM") .is_none() .then(|| PathBuf::from(env_var("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into())).into()), @@ -99,3 +104,59 @@ impl Source { } } } + +/// Environment information involving the `git` program itself. +mod git { + use bstr::{BStr, BString, ByteSlice}; + use std::process::{Command, Stdio}; + + /// Returns the file that contains git configuration coming with the installation of the `git` file in the current `PATH`, or `None` + /// if no `git` executable was found or there were other errors during execution. + pub fn install_config_path() -> Option<&'static BStr> { + static PATH: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(|| { + let mut cmd = Command::new(if cfg!(windows) { "git.exe" } else { "git" }); + cmd.args(["config", "-l", "--show-origin"]) + .stdin(Stdio::null()) + .stderr(Stdio::null()); + first_file_from_config_with_origin(cmd.output().ok()?.stdout.as_slice().into()).map(ToOwned::to_owned) + }); + PATH.as_ref().map(|b| b.as_ref()) + } + + fn first_file_from_config_with_origin(source: &BStr) -> Option<&BStr> { + let file = source.strip_prefix(b"file:")?; + let end_pos = file.find_byte(b'\t')?; + file[..end_pos].as_bstr().into() + } + + #[cfg(test)] + mod tests { + #[test] + fn first_file_from_config_with_origin() { + let macos = "file:/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig credential.helper=osxkeychain\nfile:/Users/byron/.gitconfig push.default=simple\n"; + let win_msys = + "file:C:/git-sdk-64/etc/gitconfig core.symlinks=false\r\nfile:C:/git-sdk-64/etc/gitconfig core.autocrlf=true"; + let win_cmd = "file:C:/Program Files/Git/etc/gitconfig diff.astextplain.textconv=astextplain\r\nfile:C:/Program Files/Git/etc/gitconfig filter.lfs.clean=git-lfs clean -- %f\r\n"; + let linux = "file:/home/parallels/.gitconfig core.excludesfile=~/.gitignore\n"; + let bogus = "something unexpected"; + let empty = ""; + + for (source, expected) in [ + ( + macos, + Some("/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig"), + ), + (win_msys, Some("C:/git-sdk-64/etc/gitconfig")), + (win_cmd, Some("C:/Program Files/Git/etc/gitconfig")), + (linux, Some("/home/parallels/.gitconfig")), + (bogus, None), + (empty, None), + ] { + assert_eq!( + super::first_file_from_config_with_origin(source.into()), + expected.map(Into::into) + ); + } + } + } +} diff --git a/git-config/src/types.rs b/git-config/src/types.rs index e96ea44813e..f5a75653159 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -15,6 +15,8 @@ use crate::{ /// their source. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Source { + /// A special configuration file that ships with the git installation, and is thus tied to the used git binary. + GitInstallation, /// System-wide configuration path. This is defined as /// `$(prefix)/etc/gitconfig` (where prefix is the git-installation directory). System, From 0f9833af529e35c4930f6231397368256231dcdb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 10:12:10 +0800 Subject: [PATCH 18/32] adjust to changes in `git-config` (#450) This also fixes a bug that may cause the actual System location to be ignored and replaced with the git installation location. Now it's additive. --- git-repository/src/config/cache/init.rs | 67 ++++++++++++------------- git-repository/src/env.rs | 59 ---------------------- 2 files changed, 32 insertions(+), 94 deletions(-) diff --git a/git-repository/src/config/cache/init.rs b/git-repository/src/config/cache/init.rs index 4e5ab68203c..4b5c87fd31a 100644 --- a/git-repository/src/config/cache/init.rs +++ b/git-repository/src/config/cache/init.rs @@ -55,42 +55,39 @@ impl Cache { let home_env = &home_env; let xdg_config_home_env = &xdg_config_home_env; let git_prefix = &git_prefix; - let mut install_path = use_installation.then(crate::env::git::install_config_path).flatten(); - let metas = [git_config::source::Kind::System, git_config::source::Kind::Global] - .iter() - .flat_map(|kind| kind.sources()) - .filter_map(|source| match install_path.take() { - Some(install_path) => ( - &git_config::Source::System, - git_path::from_bstr(install_path).into_owned(), - ) - .into(), - None => { - match source { - git_config::Source::System if !use_system => return None, - git_config::Source::Git if !use_git => return None, - git_config::Source::User if !use_user => return None, - _ => {} + let metas = [ + git_config::source::Kind::GitInstallation, + git_config::source::Kind::System, + git_config::source::Kind::Global, + ] + .iter() + .flat_map(|kind| kind.sources()) + .filter_map(|source| { + match source { + git_config::Source::GitInstallation if !use_installation => return None, + git_config::Source::System if !use_system => return None, + git_config::Source::Git if !use_git => return None, + git_config::Source::User if !use_user => return None, + _ => {} + } + source + .storage_location(&mut |name| { + match name { + git_ if git_.starts_with("GIT_") => Some(git_prefix), + "XDG_CONFIG_HOME" => Some(xdg_config_home_env), + "HOME" => Some(home_env), + _ => None, } - source - .storage_location(&mut |name| { - match name { - git_ if git_.starts_with("GIT_") => Some(git_prefix), - "XDG_CONFIG_HOME" => Some(xdg_config_home_env), - "HOME" => Some(home_env), - _ => None, - } - .and_then(|perm| std::env::var_os(name).and_then(|val| perm.check_opt(val))) - }) - .map(|p| (source, p.into_owned())) - } - }) - .map(|(source, path)| git_config::file::Metadata { - path: Some(path), - source: *source, - level: 0, - trust: git_sec::Trust::Full, - }); + .and_then(|perm| std::env::var_os(name).and_then(|val| perm.check_opt(val))) + }) + .map(|p| (source, p.into_owned())) + }) + .map(|(source, path)| git_config::file::Metadata { + path: Some(path), + source: *source, + level: 0, + trust: git_sec::Trust::Full, + }); let err_on_nonexisting_paths = false; let mut globals = git_config::File::from_paths_metadata_buf( diff --git a/git-repository/src/env.rs b/git-repository/src/env.rs index f2b95f1f5d8..148e197ef34 100644 --- a/git-repository/src/env.rs +++ b/git-repository/src/env.rs @@ -26,62 +26,3 @@ pub fn os_str_to_bstring(input: &OsStr) -> Result { .map(Into::into) .map_err(|_| input.to_string_lossy().into_owned()) } - -/// Environment information involving the `git` program itself. -pub mod git { - use std::process::{Command, Stdio}; - - use crate::bstr::{BStr, BString, ByteSlice}; - - /// Returns the file that contains git configuration coming with the installation of the `git` file in the current `PATH`, or `None` - /// if no `git` executable was found or there were other errors during execution. - pub fn install_config_path() -> Option<&'static BStr> { - static PATH: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(|| { - let mut cmd = Command::new(if cfg!(windows) { "git.exe" } else { "git" }); - cmd.args(["config", "-l", "--show-origin"]) - .stdin(Stdio::null()) - .stderr(Stdio::null()); - first_file_from_config_with_origin(cmd.output().ok()?.stdout.as_slice().into()).map(ToOwned::to_owned) - }); - PATH.as_ref().map(|b| b.as_ref()) - } - - fn first_file_from_config_with_origin(source: &BStr) -> Option<&BStr> { - let file = source.strip_prefix(b"file:")?; - let end_pos = file.find_byte(b'\t')?; - file[..end_pos].as_bstr().into() - } - - #[cfg(test)] - mod tests { - use crate::env::git; - - #[test] - fn first_file_from_config_with_origin() { - let macos = "file:/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig credential.helper=osxkeychain\nfile:/Users/byron/.gitconfig push.default=simple\n"; - let win_msys = - "file:C:/git-sdk-64/etc/gitconfig core.symlinks=false\r\nfile:C:/git-sdk-64/etc/gitconfig core.autocrlf=true"; - let win_cmd = "file:C:/Program Files/Git/etc/gitconfig diff.astextplain.textconv=astextplain\r\nfile:C:/Program Files/Git/etc/gitconfig filter.lfs.clean=git-lfs clean -- %f\r\n"; - let linux = "file:/home/parallels/.gitconfig core.excludesfile=~/.gitignore\n"; - let bogus = "something unexpected"; - let empty = ""; - - for (source, expected) in [ - ( - macos, - Some("/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig"), - ), - (win_msys, Some("C:/git-sdk-64/etc/gitconfig")), - (win_cmd, Some("C:/Program Files/Git/etc/gitconfig")), - (linux, Some("/home/parallels/.gitconfig")), - (bogus, None), - (empty, None), - ] { - assert_eq!( - git::first_file_from_config_with_origin(source.into()), - expected.map(Into::into) - ); - } - } - } -} From c9ff885e73c97b8712cff89bce420f3060f8bd3c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 16:02:59 +0800 Subject: [PATCH 19/32] produce only a ref-map as it contains all data somebody would want. (#450) It does more work as well, but it seems unnecessary to maintian a bigger API surface and it might be key to turning this into a step-by-step transition to fetching packs and updating local references. --- .../src/remote/connection/list_refs.rs | 148 ------------------ git-repository/src/remote/connection/mod.rs | 2 +- .../src/remote/connection/ref_map.rs | 114 ++++++++++++++ git-repository/src/remote/mod.rs | 2 +- git-repository/tests/remote/list_refs.rs | 28 ++-- 5 files changed, 129 insertions(+), 165 deletions(-) delete mode 100644 git-repository/src/remote/connection/list_refs.rs create mode 100644 git-repository/src/remote/connection/ref_map.rs diff --git a/git-repository/src/remote/connection/list_refs.rs b/git-repository/src/remote/connection/list_refs.rs deleted file mode 100644 index 660eaa4fd4a..00000000000 --- a/git-repository/src/remote/connection/list_refs.rs +++ /dev/null @@ -1,148 +0,0 @@ -use git_features::progress::Progress; -use git_protocol::transport::client::Transport; - -use crate::remote::{connection::HandshakeWithRefs, Connection, Direction}; - -/// The error returned by [`Connection::list_refs()`]. -#[derive(Debug, thiserror::Error)] -#[allow(missing_docs)] -pub enum Error { - #[error(transparent)] - Handshake(#[from] git_protocol::fetch::handshake::Error), - #[error(transparent)] - ListRefs(#[from] git_protocol::fetch::refs::Error), - #[error(transparent)] - Transport(#[from] git_protocol::transport::client::Error), - #[error(transparent)] - ConfigureCredentials(#[from] crate::config::credential_helpers::Error), -} - -impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> -where - T: Transport, - P: Progress, -{ - /// List all references on the remote. - /// - /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. - #[git_protocol::maybe_async::maybe_async] - pub async fn list_refs(mut self) -> Result, Error> { - let res = self.fetch_refs().await; - git_protocol::fetch::indicate_end_of_interaction(&mut self.transport).await?; - Ok(res?.refs) - } - - #[git_protocol::maybe_async::maybe_async] - async fn fetch_refs(&mut self) -> Result { - let mut credentials_storage; - let authenticate = match self.credentials.as_mut() { - Some(f) => f, - None => { - let url = self - .remote - .url(Direction::Fetch) - .map(ToOwned::to_owned) - .unwrap_or_else(|| { - git_url::parse(self.transport.to_url().as_bytes().into()) - .expect("valid URL to be provided by transport") - }); - credentials_storage = self.configured_credentials(url)?; - &mut credentials_storage - } - }; - let mut outcome = - git_protocol::fetch::handshake(&mut self.transport, authenticate, Vec::new(), &mut self.progress).await?; - let refs = match outcome.refs.take() { - Some(refs) => refs, - None => { - git_protocol::fetch::refs( - &mut self.transport, - outcome.server_protocol_version, - &outcome.capabilities, - |_a, _b, _c| Ok(git_protocol::fetch::delegate::LsRefsAction::Continue), - &mut self.progress, - ) - .await? - } - }; - Ok(HandshakeWithRefs { outcome, refs }) - } -} - -/// -pub mod to_map { - use crate::remote::{fetch, Connection}; - use git_features::progress::Progress; - use git_protocol::transport::client::Transport; - - /// The error returned by [`Connection::list_refs_to_map()`]. - #[derive(Debug, thiserror::Error)] - #[allow(missing_docs)] - pub enum Error { - #[error(transparent)] - ListRefs(#[from] crate::remote::list_refs::Error), - #[error(transparent)] - MappingValidation(#[from] git_refspec::match_group::validate::Error), - } - - impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> - where - T: Transport, - P: Progress, - { - /// List all references on the remote that have been filtered through our remote's [`refspecs`][crate::Remote::refspecs()] - /// for _fetching_. - /// - /// This comes in the form of all matching tips on the remote and the object they point to, along with - /// with the local tracking branch of these tips (if available). - /// - /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. - #[git_protocol::maybe_async::maybe_async] - pub async fn list_refs_to_map(mut self) -> Result { - let res = self.ref_map().await; - git_protocol::fetch::indicate_end_of_interaction(&mut self.transport) - .await - .map_err(|err| Error::ListRefs(crate::remote::list_refs::Error::Transport(err)))?; - res - } - - #[git_protocol::maybe_async::maybe_async] - async fn ref_map(&mut self) -> Result { - let remote = self.fetch_refs().await?; - let group = git_refspec::MatchGroup::from_fetch_specs(self.remote.fetch_specs.iter().map(|s| s.to_ref())); - let (res, fixes) = group - .match_remotes(remote.refs.iter().map(|r| { - let (full_ref_name, target, object) = r.unpack(); - git_refspec::match_group::Item { - full_ref_name, - target, - object, - } - })) - .validated()?; - let mappings = res.mappings; - let mappings = mappings - .into_iter() - .map(|m| fetch::Mapping { - remote: m - .item_index - .map(|idx| fetch::Source::Ref(remote.refs[idx].clone())) - .unwrap_or_else(|| { - fetch::Source::ObjectId(match m.lhs { - git_refspec::match_group::SourceRef::ObjectId(id) => id, - _ => unreachable!("no item index implies having an object id"), - }) - }), - local: m.rhs.map(|c| c.into_owned()), - spec_index: m.spec_index, - }) - .collect(); - Ok(fetch::RefMap { - mappings, - fixes, - remote_refs: remote.refs, - handshake: remote.outcome, - }) - } - } -} diff --git a/git-repository/src/remote/connection/mod.rs b/git-repository/src/remote/connection/mod.rs index 09b8342d595..0ac97e17902 100644 --- a/git-repository/src/remote/connection/mod.rs +++ b/git-repository/src/remote/connection/mod.rs @@ -66,4 +66,4 @@ mod access { } /// -pub mod list_refs; +pub mod ref_map; diff --git a/git-repository/src/remote/connection/ref_map.rs b/git-repository/src/remote/connection/ref_map.rs new file mode 100644 index 00000000000..e8b43e69bc0 --- /dev/null +++ b/git-repository/src/remote/connection/ref_map.rs @@ -0,0 +1,114 @@ +use git_features::progress::Progress; +use git_protocol::transport::client::Transport; + +use crate::remote::{connection::HandshakeWithRefs, fetch, Connection, Direction}; + +/// The error returned by [`Connection::list_refs_to_map()`]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Handshake(#[from] git_protocol::fetch::handshake::Error), + #[error(transparent)] + ListRefs(#[from] git_protocol::fetch::refs::Error), + #[error(transparent)] + Transport(#[from] git_protocol::transport::client::Error), + #[error(transparent)] + ConfigureCredentials(#[from] crate::config::credential_helpers::Error), + #[error(transparent)] + MappingValidation(#[from] git_refspec::match_group::validate::Error), +} + +impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> +where + T: Transport, + P: Progress, +{ + /// List all references on the remote that have been filtered through our remote's [`refspecs`][crate::Remote::refspecs()] + /// for _fetching_. + /// + /// This comes in the form of all matching tips on the remote and the object they point to, along with + /// with the local tracking branch of these tips (if available). + /// + /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. + #[git_protocol::maybe_async::maybe_async] + pub async fn ref_map(mut self) -> Result { + let res = self.ref_map_inner().await; + git_protocol::fetch::indicate_end_of_interaction(&mut self.transport).await?; + res + } + + #[git_protocol::maybe_async::maybe_async] + async fn ref_map_inner(&mut self) -> Result { + let remote = self.fetch_refs().await?; + let group = git_refspec::MatchGroup::from_fetch_specs(self.remote.fetch_specs.iter().map(|s| s.to_ref())); + let (res, fixes) = group + .match_remotes(remote.refs.iter().map(|r| { + let (full_ref_name, target, object) = r.unpack(); + git_refspec::match_group::Item { + full_ref_name, + target, + object, + } + })) + .validated()?; + let mappings = res.mappings; + let mappings = mappings + .into_iter() + .map(|m| fetch::Mapping { + remote: m + .item_index + .map(|idx| fetch::Source::Ref(remote.refs[idx].clone())) + .unwrap_or_else(|| { + fetch::Source::ObjectId(match m.lhs { + git_refspec::match_group::SourceRef::ObjectId(id) => id, + _ => unreachable!("no item index implies having an object id"), + }) + }), + local: m.rhs.map(|c| c.into_owned()), + spec_index: m.spec_index, + }) + .collect(); + Ok(fetch::RefMap { + mappings, + fixes, + remote_refs: remote.refs, + handshake: remote.outcome, + }) + } + #[git_protocol::maybe_async::maybe_async] + async fn fetch_refs(&mut self) -> Result { + let mut credentials_storage; + let authenticate = match self.credentials.as_mut() { + Some(f) => f, + None => { + let url = self + .remote + .url(Direction::Fetch) + .map(ToOwned::to_owned) + .unwrap_or_else(|| { + git_url::parse(self.transport.to_url().as_bytes().into()) + .expect("valid URL to be provided by transport") + }); + credentials_storage = self.configured_credentials(url)?; + &mut credentials_storage + } + }; + let mut outcome = + git_protocol::fetch::handshake(&mut self.transport, authenticate, Vec::new(), &mut self.progress).await?; + let refs = match outcome.refs.take() { + Some(refs) => refs, + None => { + git_protocol::fetch::refs( + &mut self.transport, + outcome.server_protocol_version, + &outcome.capabilities, + |_a, _b, _c| Ok(git_protocol::fetch::delegate::LsRefsAction::Continue), + &mut self.progress, + ) + .await? + } + }; + Ok(HandshakeWithRefs { outcome, refs }) + } +} diff --git a/git-repository/src/remote/mod.rs b/git-repository/src/remote/mod.rs index ee1351f2f96..2debbe7df4f 100644 --- a/git-repository/src/remote/mod.rs +++ b/git-repository/src/remote/mod.rs @@ -73,7 +73,7 @@ pub mod connect; #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] mod connection; #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] -pub use connection::{list_refs, Connection}; +pub use connection::{ref_map, Connection}; mod access; pub(crate) mod url; diff --git a/git-repository/tests/remote/list_refs.rs b/git-repository/tests/remote/list_refs.rs index 0a441998ce5..67f84673678 100644 --- a/git-repository/tests/remote/list_refs.rs +++ b/git-repository/tests/remote/list_refs.rs @@ -24,22 +24,20 @@ mod blocking_io { } let remote = repo.find_remote("origin")?; - { - let connection = remote.connect(Fetch, progress::Discard)?; - let refs = connection.list_refs()?; - assert_eq!(refs.len(), 14, "it gets all remote refs, independently of the refspec."); - } + let connection = remote.connect(Fetch, progress::Discard)?; + let map = connection.ref_map()?; + assert_eq!( + map.remote_refs.len(), + 14, + "it gets all remote refs, independently of the refspec." + ); - { - let connection = remote.connect(Fetch, progress::Discard)?; - let map = connection.list_refs_to_map()?; - assert_eq!(map.fixes.len(), 0); - assert_eq!( - map.mappings.len(), - 11, - "mappings are only a sub-set of all remotes due to refspec matching" - ); - } + assert_eq!(map.fixes.len(), 0); + assert_eq!( + map.mappings.len(), + 11, + "mappings are only a sub-set of all remotes due to refspec matching" + ); } Ok(()) } From 94c2b785f892f85503b8927c7fa98ae99d677be7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 16:05:45 +0800 Subject: [PATCH 20/32] wire up the `ref-map` sub-command. (#450) Without implementation though, which is mainly display. --- README.md | 1 + crate-status.md | 1 - gitoxide-core/src/repository/remote.rs | 18 ++++++++++++------ src/plumbing/main.rs | 10 ++++++++-- src/plumbing/options.rs | 5 ++++- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7fdab138ec6..d2fc0167c0c 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Please see _'Development Status'_ for a listing of all crates and their capabili * [x] **previous-branches** - list all previously checked out branches, powered by the ref-log. * **remote** * [x] **refs** - list all references available on the remote based on the current remote configuration. + * [x] **ref-map** - show how remote references relate to their local tracking branches as mapped by refspecs. * **credential** * [x] **fill/approve/reject** - The same as `git credential`, but implemented in Rust, calling helpers only when from trusted configuration. * **free** - no git repository necessary diff --git a/crate-status.md b/crate-status.md index 78f282293a5..cc79ffe9b2f 100644 --- a/crate-status.md +++ b/crate-status.md @@ -14,7 +14,6 @@ ### git-object * *decode (zero-copy)* borrowed objects * [x] commit - * [x] parse the title, body, and provide a title summary. * [ ] parse [trailers](https://git-scm.com/docs/git-interpret-trailers#_description) * [x] tree * encode owned objects diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index e7efce58e11..52ccc3389f8 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -1,5 +1,5 @@ #[cfg(any(feature = "blocking-client", feature = "async-client"))] -mod net { +mod refs_impl { use anyhow::bail; use git_repository as git; use git_repository::protocol::fetch; @@ -11,6 +11,11 @@ mod net { pub const PROGRESS_RANGE: std::ops::RangeInclusive = 1..=2; + pub enum Kind { + Remote, + Tracking, + } + pub struct Context { pub format: OutputFormat, pub name: Option, @@ -23,6 +28,7 @@ mod net { #[git::protocol::maybe_async::maybe_async] pub async fn refs_fn( repo: git::Repository, + _kind: refs::Kind, mut progress: impl git::Progress, out: impl std::io::Write, refs::Context { format, name, url }: refs::Context, @@ -44,17 +50,17 @@ mod net { .context("Remote didn't have a URL to connect to")? .to_bstring() )); - let refs = remote + let map = remote .connect(git::remote::Direction::Fetch, progress) .await? - .list_refs() + .ref_map() .await?; match format { - OutputFormat::Human => drop(print(out, &refs)), + OutputFormat::Human => drop(print(out, &map.remote_refs)), #[cfg(feature = "serde1")] OutputFormat::Json => { - serde_json::to_writer_pretty(out, &refs.into_iter().map(JsonRef::from).collect::>())? + serde_json::to_writer_pretty(out, &map.remote_refs.into_iter().map(JsonRef::from).collect::>())? } }; Ok(()) @@ -137,4 +143,4 @@ mod net { } } #[cfg(any(feature = "blocking-client", feature = "async-client"))] -pub use net::{refs, refs_fn as refs, JsonRef}; +pub use refs_impl::{refs, refs_fn as refs, JsonRef}; diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index baf5b6b9201..916b7eed4f4 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -122,7 +122,11 @@ pub fn main() -> Result<()> { #[cfg_attr(feature = "small", allow(unused_variables))] Subcommands::Remote(remote::Platform { name, url, cmd }) => match cmd { #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] - remote::Subcommands::Refs => { + remote::Subcommands::Refs | remote::Subcommands::RefMap => { + let kind = match cmd { + remote::Subcommands::Refs => core::repository::remote::refs::Kind::Remote, + remote::Subcommands::RefMap => core::repository::remote::refs::Kind::Tracking, + }; #[cfg(feature = "gitoxide-core-blocking-client")] { prepare_and_run( @@ -134,6 +138,7 @@ pub fn main() -> Result<()> { move |progress, out, _err| { core::repository::remote::refs( repository(Mode::LenientWithGitInstallConfig)?, + kind, progress, out, core::repository::remote::refs::Context { name, url, format }, @@ -149,7 +154,8 @@ pub fn main() -> Result<()> { Some(core::repository::remote::refs::PROGRESS_RANGE), ); futures_lite::future::block_on(core::repository::remote::refs( - repository(Mode::Lenient)?, + repository(Mode::LenientWithGitInstallConfig)?, + kind, progress, std::io::stdout(), core::repository::remote::refs::Context { name, url, format }, diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 467cb4ec024..6ae7b7066ef 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -131,9 +131,12 @@ pub mod remote { #[derive(Debug, clap::Subcommand)] #[clap(visible_alias = "remotes")] pub enum Subcommands { - /// Print all references available on the remote + /// Print all references available on the remote. #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] Refs, + /// Print all references available on the remote as filtered through ref-specs. + #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] + RefMap, } } From 4dbfa4c7a06a61caa28288a354e3ca3b68aafc82 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 16:52:47 +0800 Subject: [PATCH 21/32] first basic implementation of `ref-map` (#450) --- gitoxide-core/src/repository/remote.rs | 96 ++++++++++++++++++-------- 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index 52ccc3389f8..19f2f727b9e 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -28,11 +28,14 @@ mod refs_impl { #[git::protocol::maybe_async::maybe_async] pub async fn refs_fn( repo: git::Repository, - _kind: refs::Kind, + kind: refs::Kind, mut progress: impl git::Progress, out: impl std::io::Write, refs::Context { format, name, url }: refs::Context, ) -> anyhow::Result<()> { + if matches!(kind, refs::Kind::Tracking) && format != OutputFormat::Human { + bail!("JSON output isn't yet supported for listing ref-mappings."); + } use anyhow::Context; let remote = match (name, url) { (Some(name), None) => repo.find_remote(&name)?, @@ -56,13 +59,47 @@ mod refs_impl { .ref_map() .await?; - match format { - OutputFormat::Human => drop(print(out, &map.remote_refs)), - #[cfg(feature = "serde1")] - OutputFormat::Json => { - serde_json::to_writer_pretty(out, &map.remote_refs.into_iter().map(JsonRef::from).collect::>())? + match kind { + refs::Kind::Tracking => print_refmap(remote, map, out), + refs::Kind::Remote => { + match format { + OutputFormat::Human => drop(print(out, &map.remote_refs)), + #[cfg(feature = "serde1")] + OutputFormat::Json => serde_json::to_writer_pretty( + out, + &map.remote_refs.into_iter().map(JsonRef::from).collect::>(), + )?, + }; + Ok(()) } - }; + } + } + + fn print_refmap( + remote: git::Remote<'_>, + mut map: git::remote::fetch::RefMap, + mut out: impl std::io::Write, + ) -> anyhow::Result<()> { + let mut last_spec_index = usize::MAX; + map.mappings.sort_by_key(|m| m.spec_index); + for mapping in &map.mappings { + if mapping.spec_index != last_spec_index { + last_spec_index = mapping.spec_index; + let spec = &remote.refspecs(git::remote::Direction::Fetch)[mapping.spec_index]; + spec.to_ref().write_to(&mut out)?; + writeln!(out)?; + } + + write!(out, "\t")?; + match &mapping.remote { + git::remote::fetch::Source::ObjectId(id) => write!(out, "{}", id), + git::remote::fetch::Source::Ref(r) => print_ref(&mut out, r), + }?; + match &mapping.local { + Some(local) => writeln!(out, " -> {}", local), + None => writeln!(out, " (fetch only)"), + }?; + } Ok(()) } @@ -116,28 +153,33 @@ mod refs_impl { } } + fn print_ref(mut out: impl std::io::Write, r: &fetch::Ref) -> std::io::Result<()> { + match r { + fetch::Ref::Direct { + full_ref_name: path, + object, + } => write!(&mut out, "{} {}", object.to_hex(), path), + fetch::Ref::Peeled { + full_ref_name: path, + object, + tag, + } => { + write!(&mut out, "{} {} tag:{}", object.to_hex(), path, tag) + } + fetch::Ref::Symbolic { + full_ref_name: path, + target, + object, + } => { + write!(&mut out, "{} {} symref-target:{}", object.to_hex(), path, target) + } + } + } + pub(crate) fn print(mut out: impl std::io::Write, refs: &[fetch::Ref]) -> std::io::Result<()> { for r in refs { - match r { - fetch::Ref::Direct { - full_ref_name: path, - object, - } => writeln!(&mut out, "{} {}", object.to_hex(), path), - fetch::Ref::Peeled { - full_ref_name: path, - object, - tag, - } => { - writeln!(&mut out, "{} {} tag:{}", object.to_hex(), path, tag) - } - fetch::Ref::Symbolic { - full_ref_name: path, - target, - object, - } => { - writeln!(&mut out, "{} {} symref-target:{}", object.to_hex(), path, target) - } - }?; + print_ref(&mut out, r)?; + writeln!(out)?; } Ok(()) } From 6f60a793297e2a29cf835591add6669c067da3e5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 17:06:06 +0800 Subject: [PATCH 22/32] better refmap printing (#450) --- git-ref/src/name.rs | 10 +++++++ gitoxide-core/src/repository/remote.rs | 41 ++++++++++++++++---------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/git-ref/src/name.rs b/git-ref/src/name.rs index 4162a69fa31..edfe48e901a 100644 --- a/git-ref/src/name.rs +++ b/git-ref/src/name.rs @@ -168,6 +168,16 @@ mod impls { } } +impl<'a> convert::TryFrom<&'a BString> for &'a PartialNameRef { + type Error = Error; + + fn try_from(v: &'a BString) -> Result { + Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial( + v.as_ref(), + )?)) + } +} + impl<'a> convert::TryFrom<&'a BStr> for &'a PartialNameRef { type Error = Error; diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index 19f2f727b9e..d6e38ff1037 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -60,7 +60,7 @@ mod refs_impl { .await?; match kind { - refs::Kind::Tracking => print_refmap(remote, map, out), + refs::Kind::Tracking => print_refmap(&repo, remote, map, out), refs::Kind::Remote => { match format { OutputFormat::Human => drop(print(out, &map.remote_refs)), @@ -76,6 +76,7 @@ mod refs_impl { } fn print_refmap( + repo: &git::Repository, remote: git::Remote<'_>, mut map: git::remote::fetch::RefMap, mut out: impl std::io::Write, @@ -91,12 +92,26 @@ mod refs_impl { } write!(out, "\t")?; - match &mapping.remote { - git::remote::fetch::Source::ObjectId(id) => write!(out, "{}", id), - git::remote::fetch::Source::Ref(r) => print_ref(&mut out, r), - }?; + let target_id = match &mapping.remote { + git::remote::fetch::Source::ObjectId(id) => { + write!(out, "{}", id)?; + id + } + git::remote::fetch::Source::Ref(r) => print_ref(&mut out, r)?, + }; match &mapping.local { - Some(local) => writeln!(out, " -> {}", local), + Some(local) => { + write!(out, " -> {} ", local)?; + match repo.try_find_reference(local)? { + Some(mut tracking) => { + let msg = (tracking.peel_to_id_in_place()?.as_ref() == target_id) + .then(|| "[up-to-date]") + .unwrap_or("[changed]"); + writeln!(out, "{msg}") + } + None => writeln!(out, "[new]"), + } + } None => writeln!(out, " (fetch only)"), }?; } @@ -153,26 +168,22 @@ mod refs_impl { } } - fn print_ref(mut out: impl std::io::Write, r: &fetch::Ref) -> std::io::Result<()> { + fn print_ref(mut out: impl std::io::Write, r: &fetch::Ref) -> std::io::Result<&git::hash::oid> { match r { fetch::Ref::Direct { full_ref_name: path, object, - } => write!(&mut out, "{} {}", object.to_hex(), path), + } => write!(&mut out, "{} {}", object.to_hex(), path).map(|_| object.as_ref()), fetch::Ref::Peeled { full_ref_name: path, - object, tag, - } => { - write!(&mut out, "{} {} tag:{}", object.to_hex(), path, tag) - } + object, + } => write!(&mut out, "{} {} tag:{}", object.to_hex(), path, tag).map(|_| tag.as_ref()), fetch::Ref::Symbolic { full_ref_name: path, target, object, - } => { - write!(&mut out, "{} {} symref-target:{}", object.to_hex(), path, target) - } + } => write!(&mut out, "{} {} symref-target:{}", object.to_hex(), path, target).map(|_| object.as_ref()), } } From d8f160826cd6446e0422aef6020e1413895f340e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 17:31:15 +0800 Subject: [PATCH 23/32] Add method to allow replacing a Remote's refspecs entirely. (#450) --- git-repository/src/remote/access.rs | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/git-repository/src/remote/access.rs b/git-repository/src/remote/access.rs index 6d6e18aa45a..7a194f94531 100644 --- a/git-repository/src/remote/access.rs +++ b/git-repository/src/remote/access.rs @@ -1,5 +1,6 @@ use git_refspec::RefSpec; +use crate::bstr::BStr; use crate::{remote, Remote}; /// Access @@ -60,4 +61,36 @@ impl Remote<'_> { url_err.or(push_url_err).map(Err::<&mut Self, _>).transpose()?; Ok(self) } + + /// Replace all currently set refspecs, typically from configuration, with the given `specs` for `direction`, + /// or `None` if one of the input specs could not be parsed. + pub fn replace_refspecs( + &mut self, + specs: impl IntoIterator, + direction: remote::Direction, + ) -> Result<(), git_refspec::parse::Error> + where + Spec: AsRef, + { + use remote::Direction::*; + let specs: Vec<_> = specs + .into_iter() + .map(|spec| { + git_refspec::parse( + spec.as_ref(), + match direction { + Push => git_refspec::parse::Operation::Push, + Fetch => git_refspec::parse::Operation::Fetch, + }, + ) + .map(|url| url.to_owned()) + }) + .collect::>()?; + let dst = match direction { + Push => &mut self.push_specs, + Fetch => &mut self.fetch_specs, + }; + *dst = specs; + Ok(()) + } } From f4d8198992b4c45f64d81e20f40a1cad69883162 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 17:32:21 +0800 Subject: [PATCH 24/32] Correct printing of tag information (even though it doesn't look great) (#450) --- gitoxide-core/src/repository/remote.rs | 27 ++++++++++++++++---------- src/plumbing/main.rs | 6 ++++-- src/plumbing/options.rs | 7 ++++++- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index d6e38ff1037..3af80f9c528 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -8,12 +8,13 @@ mod refs_impl { pub mod refs { use crate::OutputFormat; + use git_repository::bstr::BString; pub const PROGRESS_RANGE: std::ops::RangeInclusive = 1..=2; pub enum Kind { Remote, - Tracking, + Tracking { ref_specs: Vec }, } pub struct Context { @@ -33,11 +34,8 @@ mod refs_impl { out: impl std::io::Write, refs::Context { format, name, url }: refs::Context, ) -> anyhow::Result<()> { - if matches!(kind, refs::Kind::Tracking) && format != OutputFormat::Human { - bail!("JSON output isn't yet supported for listing ref-mappings."); - } use anyhow::Context; - let remote = match (name, url) { + let mut remote = match (name, url) { (Some(name), None) => repo.find_remote(&name)?, (None, None) => repo .head()? @@ -46,6 +44,12 @@ mod refs_impl { (None, Some(url)) => repo.remote_at(url)?, (Some(_), Some(_)) => bail!("Must not set both the remote name and the url - they are mutually exclusive"), }; + if let refs::Kind::Tracking { ref_specs } = &kind { + if format != OutputFormat::Human { + bail!("JSON output isn't yet supported for listing ref-mappings."); + } + remote.replace_refspecs(ref_specs.iter(), git::remote::Direction::Fetch)?; + } progress.info(format!( "Connecting to {:?}", remote @@ -60,7 +64,7 @@ mod refs_impl { .await?; match kind { - refs::Kind::Tracking => print_refmap(&repo, remote, map, out), + refs::Kind::Tracking { .. } => print_refmap(&repo, remote, map, out), refs::Kind::Remote => { match format { OutputFormat::Human => drop(print(out, &map.remote_refs)), @@ -103,10 +107,13 @@ mod refs_impl { Some(local) => { write!(out, " -> {} ", local)?; match repo.try_find_reference(local)? { - Some(mut tracking) => { - let msg = (tracking.peel_to_id_in_place()?.as_ref() == target_id) - .then(|| "[up-to-date]") - .unwrap_or("[changed]"); + Some(tracking) => { + let msg = match tracking.try_id() { + Some(id) => (id.as_ref() == target_id) + .then(|| "[up-to-date]") + .unwrap_or("[changed]"), + None => "[skipped]", + }; writeln!(out, "{msg}") } None => writeln!(out, "[new]"), diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 916b7eed4f4..304679fd302 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -122,10 +122,12 @@ pub fn main() -> Result<()> { #[cfg_attr(feature = "small", allow(unused_variables))] Subcommands::Remote(remote::Platform { name, url, cmd }) => match cmd { #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] - remote::Subcommands::Refs | remote::Subcommands::RefMap => { + remote::Subcommands::Refs | remote::Subcommands::RefMap { .. } => { let kind = match cmd { remote::Subcommands::Refs => core::repository::remote::refs::Kind::Remote, - remote::Subcommands::RefMap => core::repository::remote::refs::Kind::Tracking, + remote::Subcommands::RefMap { ref_spec } => { + core::repository::remote::refs::Kind::Tracking { ref_specs: ref_spec } + } }; #[cfg(feature = "gitoxide-core-blocking-client")] { diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 6ae7b7066ef..a7f3fd4bad9 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -110,6 +110,7 @@ pub mod config { pub mod remote { use git_repository as git; + use git_repository::bstr::BString; #[derive(Debug, clap::Parser)] pub struct Platform { @@ -136,7 +137,11 @@ pub mod remote { Refs, /// Print all references available on the remote as filtered through ref-specs. #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] - RefMap, + RefMap { + /// Override the built-in and configured ref-specs with one or more of the given ones. + #[clap(parse(try_from_os_str = git::env::os_str_to_bstring))] + ref_spec: Vec, + }, } } From 2237495d82624b39bf75c6430549424a5e36b8bb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 17:39:02 +0800 Subject: [PATCH 25/32] show fixes as well (#450) --- git-repository/src/remote/connection/ref_map.rs | 2 +- gitoxide-core/src/repository/remote.rs | 12 ++++++++++-- src/plumbing/main.rs | 4 +++- src/plumbing/options.rs | 3 +-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/git-repository/src/remote/connection/ref_map.rs b/git-repository/src/remote/connection/ref_map.rs index e8b43e69bc0..4e982110ae2 100644 --- a/git-repository/src/remote/connection/ref_map.rs +++ b/git-repository/src/remote/connection/ref_map.rs @@ -3,7 +3,7 @@ use git_protocol::transport::client::Transport; use crate::remote::{connection::HandshakeWithRefs, fetch, Connection, Direction}; -/// The error returned by [`Connection::list_refs_to_map()`]. +/// The error returned by [`Connection::ref_map()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index 3af80f9c528..b4a08dcc0bf 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -32,6 +32,7 @@ mod refs_impl { kind: refs::Kind, mut progress: impl git::Progress, out: impl std::io::Write, + err: impl std::io::Write, refs::Context { format, name, url }: refs::Context, ) -> anyhow::Result<()> { use anyhow::Context; @@ -64,7 +65,7 @@ mod refs_impl { .await?; match kind { - refs::Kind::Tracking { .. } => print_refmap(&repo, remote, map, out), + refs::Kind::Tracking { .. } => print_refmap(&repo, remote, map, out, err), refs::Kind::Remote => { match format { OutputFormat::Human => drop(print(out, &map.remote_refs)), @@ -84,6 +85,7 @@ mod refs_impl { remote: git::Remote<'_>, mut map: git::remote::fetch::RefMap, mut out: impl std::io::Write, + mut err: impl std::io::Write, ) -> anyhow::Result<()> { let mut last_spec_index = usize::MAX; map.mappings.sort_by_key(|m| m.spec_index); @@ -105,7 +107,7 @@ mod refs_impl { }; match &mapping.local { Some(local) => { - write!(out, " -> {} ", local)?; + write!(out, " -> {local} ")?; match repo.try_find_reference(local)? { Some(tracking) => { let msg = match tracking.try_id() { @@ -122,6 +124,12 @@ mod refs_impl { None => writeln!(out, " (fetch only)"), }?; } + if !map.fixes.is_empty() { + writeln!(err, "Fixes and sanitizations")?; + for fix in &map.fixes { + writeln!(err, "\t{fix:?}")?; + } + } Ok(()) } diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 304679fd302..4fef57ea873 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -137,12 +137,13 @@ pub fn main() -> Result<()> { progress, progress_keep_open, core::repository::remote::refs::PROGRESS_RANGE, - move |progress, out, _err| { + move |progress, out, err| { core::repository::remote::refs( repository(Mode::LenientWithGitInstallConfig)?, kind, progress, out, + err, core::repository::remote::refs::Context { name, url, format }, ) }, @@ -160,6 +161,7 @@ pub fn main() -> Result<()> { kind, progress, std::io::stdout(), + std::io::stderr(), core::repository::remote::refs::Context { name, url, format }, )) } diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index a7f3fd4bad9..27285959bfd 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -110,7 +110,6 @@ pub mod config { pub mod remote { use git_repository as git; - use git_repository::bstr::BString; #[derive(Debug, clap::Parser)] pub struct Platform { @@ -140,7 +139,7 @@ pub mod remote { RefMap { /// Override the built-in and configured ref-specs with one or more of the given ones. #[clap(parse(try_from_os_str = git::env::os_str_to_bstring))] - ref_spec: Vec, + ref_spec: Vec, }, } } From e819fc68531e2d2de3d7df782f63f84941eeef57 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 18:08:35 +0800 Subject: [PATCH 26/32] A more efficient representation for `validate::Fix` (#450) --- git-refspec/src/match_group/validate.rs | 9 +++++---- git-refspec/tests/match_group/mod.rs | 9 +++++---- git-refspec/tests/matching/mod.rs | 15 ++++++++++----- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/git-refspec/src/match_group/validate.rs b/git-refspec/src/match_group/validate.rs index c1f30ce8ae8..4cd47b35b38 100644 --- a/git-refspec/src/match_group/validate.rs +++ b/git-refspec/src/match_group/validate.rs @@ -1,4 +1,5 @@ use crate::match_group::{Outcome, Source}; +use crate::RefSpecRef; use bstr::BString; use std::collections::BTreeMap; @@ -44,13 +45,13 @@ impl std::fmt::Display for Issue { /// All possible fixes corrected while validating matched mappings. #[derive(Debug, PartialEq, Eq, Clone)] -pub enum Fix { +pub enum Fix<'a> { /// Removed a mapping that contained a partial destination entirely. MappingWithPartialDestinationRemoved { /// The destination ref name that was ignored. name: BString, /// The spec that defined the mapping - spec: BString, + spec: RefSpecRef<'a>, }, } @@ -86,7 +87,7 @@ impl<'spec, 'item> Outcome<'spec, 'item> { /// Return `(modified self, issues)` providing a fixed-up set of mappings in `self` with the fixed `issues` /// provided as part of it. /// Terminal issues are communicated using the [`Error`] type accordingly. - pub fn validated(mut self) -> Result<(Self, Vec), Error> { + pub fn validated(mut self) -> Result<(Self, Vec>), Error> { let mut sources_by_destinations = BTreeMap::new(); for (dst, (spec_index, src)) in self .mappings @@ -121,7 +122,7 @@ impl<'spec, 'item> Outcome<'spec, 'item> { } else { fixed.push(Fix::MappingWithPartialDestinationRemoved { name: dst.as_ref().to_owned(), - spec: group.specs[m.spec_index].to_bstring(), + spec: group.specs[m.spec_index], }); false } diff --git a/git-refspec/tests/match_group/mod.rs b/git-refspec/tests/match_group/mod.rs index fade345bb04..170d0b270bc 100644 --- a/git-refspec/tests/match_group/mod.rs +++ b/git-refspec/tests/match_group/mod.rs @@ -50,7 +50,7 @@ mod single { mod multiple { use crate::matching::baseline; use git_refspec::match_group::validate::Fix; - use git_refspec::parse::Error; + use git_refspec::parse::{Error, Operation}; #[test] fn fetch_only() { @@ -160,20 +160,21 @@ mod multiple { #[test] fn fetch_and_update_with_fixes() { let glob_spec = "refs/heads/f*:foo/f*"; + let glob_spec_ref = git_refspec::parse(glob_spec.into(), Operation::Fetch).unwrap(); baseline::agrees_and_applies_fixes( [glob_spec, "f1:f1"], [ Fix::MappingWithPartialDestinationRemoved { name: "foo/f1".into(), - spec: glob_spec.into(), + spec: glob_spec_ref, }, Fix::MappingWithPartialDestinationRemoved { name: "foo/f2".into(), - spec: glob_spec.into(), + spec: glob_spec_ref, }, Fix::MappingWithPartialDestinationRemoved { name: "foo/f3".into(), - spec: glob_spec.into(), + spec: glob_spec_ref, }, ], ["refs/heads/f1:refs/heads/f1"], diff --git a/git-refspec/tests/matching/mod.rs b/git-refspec/tests/matching/mod.rs index bc03b751523..a68ab3aa50f 100644 --- a/git-refspec/tests/matching/mod.rs +++ b/git-refspec/tests/matching/mod.rs @@ -54,9 +54,9 @@ pub mod baseline { agrees_and_applies_fixes(specs, Vec::new(), expected) } - pub fn agrees_and_applies_fixes<'a, 'b>( + pub fn agrees_and_applies_fixes<'a, 'b, 'c>( specs: impl IntoIterator + Clone, - fixes: impl IntoIterator, + fixes: impl IntoIterator>, expected: impl IntoIterator, ) { check_fetch_remote( @@ -125,9 +125,14 @@ pub mod baseline { of_objects_with_destinations_are_written_into_given_local_branches(specs, expected) } - enum Mode { - Normal { validate_err: Option }, - Custom { expected: Vec, fixes: Vec }, + enum Mode<'a> { + Normal { + validate_err: Option, + }, + Custom { + expected: Vec, + fixes: Vec>, + }, } fn check_fetch_remote<'a>(specs: impl IntoIterator + Clone, mode: Mode) { From 91d1a3abf02841c9b43fd8a3102375315a6db160 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 19:18:21 +0800 Subject: [PATCH 27/32] adapt to changes in `git-refspec` (#450) --- git-repository/src/remote/connection/ref_map.rs | 6 +++--- git-repository/src/remote/mod.rs | 4 ++-- gitoxide-core/src/repository/remote.rs | 11 +++++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/git-repository/src/remote/connection/ref_map.rs b/git-repository/src/remote/connection/ref_map.rs index 4e982110ae2..307976f4991 100644 --- a/git-repository/src/remote/connection/ref_map.rs +++ b/git-repository/src/remote/connection/ref_map.rs @@ -19,7 +19,7 @@ pub enum Error { MappingValidation(#[from] git_refspec::match_group::validate::Error), } -impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> +impl<'remote, 'repo, T, P> Connection<'remote, 'repo, T, P> where T: Transport, P: Progress, @@ -32,14 +32,14 @@ where /// /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. #[git_protocol::maybe_async::maybe_async] - pub async fn ref_map(mut self) -> Result { + pub async fn ref_map(mut self) -> Result, Error> { let res = self.ref_map_inner().await; git_protocol::fetch::indicate_end_of_interaction(&mut self.transport).await?; res } #[git_protocol::maybe_async::maybe_async] - async fn ref_map_inner(&mut self) -> Result { + async fn ref_map_inner(&mut self) -> Result, Error> { let remote = self.fetch_refs().await?; let group = git_refspec::MatchGroup::from_fetch_specs(self.remote.fetch_specs.iter().map(|s| s.to_ref())); let (res, fixes) = group diff --git a/git-repository/src/remote/mod.rs b/git-repository/src/remote/mod.rs index 2debbe7df4f..e1bed940331 100644 --- a/git-repository/src/remote/mod.rs +++ b/git-repository/src/remote/mod.rs @@ -32,11 +32,11 @@ pub mod fetch { /// Information about the relationship between our refspecs, and remote references with their local counterparts. #[derive(Debug, Clone)] - pub struct RefMap { + pub struct RefMap<'spec> { /// A mapping between a remote reference and a local tracking branch. pub mappings: Vec, /// Information about the fixes applied to the `mapping` due to validation and sanitization. - pub fixes: Vec, + pub fixes: Vec>, /// All refs advertised by the remote. pub remote_refs: Vec, /// Additional information provided by the server as part of the handshake. diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index b4a08dcc0bf..3378aad7e32 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -3,6 +3,7 @@ mod refs_impl { use anyhow::bail; use git_repository as git; use git_repository::protocol::fetch; + use git_repository::refspec::RefSpec; use crate::OutputFormat; @@ -65,7 +66,9 @@ mod refs_impl { .await?; match kind { - refs::Kind::Tracking { .. } => print_refmap(&repo, remote, map, out, err), + refs::Kind::Tracking { .. } => { + print_refmap(&repo, remote.refspecs(git::remote::Direction::Fetch), map, out, err) + } refs::Kind::Remote => { match format { OutputFormat::Human => drop(print(out, &map.remote_refs)), @@ -82,8 +85,8 @@ mod refs_impl { fn print_refmap( repo: &git::Repository, - remote: git::Remote<'_>, - mut map: git::remote::fetch::RefMap, + refspecs: &[RefSpec], + mut map: git::remote::fetch::RefMap<'_>, mut out: impl std::io::Write, mut err: impl std::io::Write, ) -> anyhow::Result<()> { @@ -92,7 +95,7 @@ mod refs_impl { for mapping in &map.mappings { if mapping.spec_index != last_spec_index { last_spec_index = mapping.spec_index; - let spec = &remote.refspecs(git::remote::Direction::Fetch)[mapping.spec_index]; + let spec = &refspecs[mapping.spec_index]; spec.to_ref().write_to(&mut out)?; writeln!(out)?; } From fec6db8fc3fb13ba15b032751d0414da93adb113 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 20:04:27 +0800 Subject: [PATCH 28/32] nicer printing of fixes (#450) --- gitoxide-core/src/repository/remote.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index 3378aad7e32..6b65036f506 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -3,6 +3,7 @@ mod refs_impl { use anyhow::bail; use git_repository as git; use git_repository::protocol::fetch; + use git_repository::refspec::match_group::validate::Fix; use git_repository::refspec::RefSpec; use crate::OutputFormat; @@ -128,9 +129,25 @@ mod refs_impl { }?; } if !map.fixes.is_empty() { - writeln!(err, "Fixes and sanitizations")?; + writeln!( + err, + "The following destination refs were removed as they didn't start with 'ref/'" + )?; + map.fixes.sort_by_key(|f| match f { + Fix::MappingWithPartialDestinationRemoved { spec, .. } => *spec, + }); + let mut prev_spec = None; for fix in &map.fixes { - writeln!(err, "\t{fix:?}")?; + match fix { + Fix::MappingWithPartialDestinationRemoved { name, spec } => { + if prev_spec.map_or(true, |prev_spec| prev_spec != spec) { + prev_spec = spec.into(); + spec.write_to(&mut err)?; + writeln!(err)?; + } + writeln!(err, "\t{name}")?; + } + } } } Ok(()) From 098961f9842f8c7d6f6254eb1ffe83fed8a403ce Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 20:06:36 +0800 Subject: [PATCH 29/32] don't print tags as target, as it's misleading (#450) --- gitoxide-core/src/repository/remote.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index 6b65036f506..c5eddd89308 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -208,17 +208,17 @@ mod refs_impl { fetch::Ref::Direct { full_ref_name: path, object, - } => write!(&mut out, "{} {}", object.to_hex(), path).map(|_| object.as_ref()), + } => write!(&mut out, "{} {}", object, path).map(|_| object.as_ref()), fetch::Ref::Peeled { full_ref_name: path, tag, object, - } => write!(&mut out, "{} {} tag:{}", object.to_hex(), path, tag).map(|_| tag.as_ref()), + } => write!(&mut out, "{} {} object:{}", tag, path, object).map(|_| tag.as_ref()), fetch::Ref::Symbolic { full_ref_name: path, target, object, - } => write!(&mut out, "{} {} symref-target:{}", object.to_hex(), path, target).map(|_| object.as_ref()), + } => write!(&mut out, "{} {} symref-target:{}", object, path, target).map(|_| object.as_ref()), } } From 4720666c8bfdaa3acc5c832b44755d4b4f86e16e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 20:20:52 +0800 Subject: [PATCH 30/32] option to print server information about the connection (#450) --- git-protocol/src/fetch/handshake.rs | 1 + git-transport/src/client/capabilities.rs | 1 + gitoxide-core/src/repository/remote.rs | 24 ++++++++++++++++++------ src/plumbing/main.rs | 10 +++++++--- src/plumbing/options.rs | 3 +++ 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/git-protocol/src/fetch/handshake.rs b/git-protocol/src/fetch/handshake.rs index 895c6ac0366..78d71095339 100644 --- a/git-protocol/src/fetch/handshake.rs +++ b/git-protocol/src/fetch/handshake.rs @@ -4,6 +4,7 @@ use crate::fetch::Ref; /// The result of the [`handshake()`][super::handshake()] function. #[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Outcome { /// The protocol version the server responded with. It might have downgraded the desired version. pub server_protocol_version: git_transport::Protocol, diff --git a/git-transport/src/client/capabilities.rs b/git-transport/src/client/capabilities.rs index 3bda61ade18..f6e1efb1513 100644 --- a/git-transport/src/client/capabilities.rs +++ b/git-transport/src/client/capabilities.rs @@ -24,6 +24,7 @@ pub enum Error { /// A structure to represent multiple [capabilities][Capability] or features supported by the server. #[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Capabilities { data: BString, value_sep: u8, diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index c5eddd89308..7b191f2543b 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -16,7 +16,7 @@ mod refs_impl { pub enum Kind { Remote, - Tracking { ref_specs: Vec }, + Tracking { ref_specs: Vec, server_info: bool }, } pub struct Context { @@ -47,11 +47,13 @@ mod refs_impl { (None, Some(url)) => repo.remote_at(url)?, (Some(_), Some(_)) => bail!("Must not set both the remote name and the url - they are mutually exclusive"), }; - if let refs::Kind::Tracking { ref_specs } = &kind { + if let refs::Kind::Tracking { ref_specs, .. } = &kind { if format != OutputFormat::Human { bail!("JSON output isn't yet supported for listing ref-mappings."); } - remote.replace_refspecs(ref_specs.iter(), git::remote::Direction::Fetch)?; + if !ref_specs.is_empty() { + remote.replace_refspecs(ref_specs.iter(), git::remote::Direction::Fetch)?; + } } progress.info(format!( "Connecting to {:?}", @@ -67,9 +69,14 @@ mod refs_impl { .await?; match kind { - refs::Kind::Tracking { .. } => { - print_refmap(&repo, remote.refspecs(git::remote::Direction::Fetch), map, out, err) - } + refs::Kind::Tracking { server_info, .. } => print_refmap( + &repo, + remote.refspecs(git::remote::Direction::Fetch), + map, + server_info, + out, + err, + ), refs::Kind::Remote => { match format { OutputFormat::Human => drop(print(out, &map.remote_refs)), @@ -88,6 +95,7 @@ mod refs_impl { repo: &git::Repository, refspecs: &[RefSpec], mut map: git::remote::fetch::RefMap<'_>, + server_info: bool, mut out: impl std::io::Write, mut err: impl std::io::Write, ) -> anyhow::Result<()> { @@ -150,6 +158,10 @@ mod refs_impl { } } } + if server_info { + writeln!(out, "Additional Information")?; + writeln!(out, "{:?}", map.handshake)?; + } Ok(()) } diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 4fef57ea873..bc7f7f38f97 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -125,9 +125,13 @@ pub fn main() -> Result<()> { remote::Subcommands::Refs | remote::Subcommands::RefMap { .. } => { let kind = match cmd { remote::Subcommands::Refs => core::repository::remote::refs::Kind::Remote, - remote::Subcommands::RefMap { ref_spec } => { - core::repository::remote::refs::Kind::Tracking { ref_specs: ref_spec } - } + remote::Subcommands::RefMap { + ref_spec, + connection_info, + } => core::repository::remote::refs::Kind::Tracking { + ref_specs: ref_spec, + server_info: connection_info, + }, }; #[cfg(feature = "gitoxide-core-blocking-client")] { diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 27285959bfd..9ab5296ec58 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -137,6 +137,9 @@ pub mod remote { /// Print all references available on the remote as filtered through ref-specs. #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] RefMap { + /// Output additional typically internal information provided by the server. + #[clap(long)] + connection_info: bool, /// Override the built-in and configured ref-specs with one or more of the given ones. #[clap(parse(try_from_os_str = git::env::os_str_to_bstring))] ref_spec: Vec, From 11851f334f642e7bd69bcbfc7ad4f1990fc326ba Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 20:25:49 +0800 Subject: [PATCH 31/32] refactor (#450) --- gitoxide-core/src/repository/remote.rs | 32 +++++++++++++------------- src/plumbing/main.rs | 27 ++++++++++++++-------- src/plumbing/options.rs | 7 +++--- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index 7b191f2543b..1d1cb4c2280 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -16,13 +16,14 @@ mod refs_impl { pub enum Kind { Remote, - Tracking { ref_specs: Vec, server_info: bool }, + Tracking { ref_specs: Vec }, } pub struct Context { pub format: OutputFormat, pub name: Option, pub url: Option, + pub handshake_info: bool, } pub(crate) use super::print; @@ -33,9 +34,14 @@ mod refs_impl { repo: git::Repository, kind: refs::Kind, mut progress: impl git::Progress, - out: impl std::io::Write, + mut out: impl std::io::Write, err: impl std::io::Write, - refs::Context { format, name, url }: refs::Context, + refs::Context { + format, + name, + url, + handshake_info, + }: refs::Context, ) -> anyhow::Result<()> { use anyhow::Context; let mut remote = match (name, url) { @@ -68,15 +74,14 @@ mod refs_impl { .ref_map() .await?; + if handshake_info { + writeln!(out, "Handshake Information")?; + writeln!(out, "\t{:?}", map.handshake)?; + } match kind { - refs::Kind::Tracking { server_info, .. } => print_refmap( - &repo, - remote.refspecs(git::remote::Direction::Fetch), - map, - server_info, - out, - err, - ), + refs::Kind::Tracking { .. } => { + print_refmap(&repo, remote.refspecs(git::remote::Direction::Fetch), map, out, err) + } refs::Kind::Remote => { match format { OutputFormat::Human => drop(print(out, &map.remote_refs)), @@ -95,7 +100,6 @@ mod refs_impl { repo: &git::Repository, refspecs: &[RefSpec], mut map: git::remote::fetch::RefMap<'_>, - server_info: bool, mut out: impl std::io::Write, mut err: impl std::io::Write, ) -> anyhow::Result<()> { @@ -158,10 +162,6 @@ mod refs_impl { } } } - if server_info { - writeln!(out, "Additional Information")?; - writeln!(out, "{:?}", map.handshake)?; - } Ok(()) } diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index bc7f7f38f97..5c587d488dd 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -120,18 +120,25 @@ pub fn main() -> Result<()> { }, ), #[cfg_attr(feature = "small", allow(unused_variables))] - Subcommands::Remote(remote::Platform { name, url, cmd }) => match cmd { + Subcommands::Remote(remote::Platform { + name, + url, + cmd, + handshake_info, + }) => match cmd { #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] remote::Subcommands::Refs | remote::Subcommands::RefMap { .. } => { let kind = match cmd { remote::Subcommands::Refs => core::repository::remote::refs::Kind::Remote, - remote::Subcommands::RefMap { - ref_spec, - connection_info, - } => core::repository::remote::refs::Kind::Tracking { - ref_specs: ref_spec, - server_info: connection_info, - }, + remote::Subcommands::RefMap { ref_spec } => { + core::repository::remote::refs::Kind::Tracking { ref_specs: ref_spec } + } + }; + let context = core::repository::remote::refs::Context { + name, + url, + format, + handshake_info, }; #[cfg(feature = "gitoxide-core-blocking-client")] { @@ -148,7 +155,7 @@ pub fn main() -> Result<()> { progress, out, err, - core::repository::remote::refs::Context { name, url, format }, + context, ) }, ) @@ -166,7 +173,7 @@ pub fn main() -> Result<()> { progress, std::io::stdout(), std::io::stderr(), - core::repository::remote::refs::Context { name, url, format }, + context, )) } } diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 9ab5296ec58..663773cc745 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -123,6 +123,10 @@ pub mod remote { #[clap(long, short = 'u', conflicts_with("name"), parse(try_from_os_str = std::convert::TryFrom::try_from))] pub url: Option, + /// Output additional typically information provided by the server as part of the connection handshake. + #[clap(long)] + pub handshake_info: bool, + /// Subcommands #[clap(subcommand)] pub cmd: Subcommands, @@ -137,9 +141,6 @@ pub mod remote { /// Print all references available on the remote as filtered through ref-specs. #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] RefMap { - /// Output additional typically internal information provided by the server. - #[clap(long)] - connection_info: bool, /// Override the built-in and configured ref-specs with one or more of the given ones. #[clap(parse(try_from_os_str = git::env::os_str_to_bstring))] ref_spec: Vec, From 461ff27727336be816571aa85e0b4d117ea67c7f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Sep 2022 20:47:45 +0800 Subject: [PATCH 32/32] fix journeytests (#450) --- .../plumbing/no-repo/pack/receive/file-v-any-no-output | 2 +- .../plumbing/no-repo/pack/receive/file-v-any-with-output | 2 +- tests/snapshots/plumbing/repository/remote/refs/file-v-any | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output index 57c0617c306..b908a0b0987 100644 --- a/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output +++ b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output @@ -4,5 +4,5 @@ pack: 346574b7331dc3a1724da218d622c6e1b6c66a57 3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 HEAD symref-target:refs/heads/main ee3c97678e89db4eab7420b04aef51758359f152 refs/heads/dev 3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/tags/annotated tag:feae03400632392a7f38e5b2775f98a439f5eaf5 +feae03400632392a7f38e5b2775f98a439f5eaf5 refs/tags/annotated object:ee3c97678e89db4eab7420b04aef51758359f152 efa596d621559707b2d221f10490959b2decbc6c refs/tags/unannotated \ No newline at end of file diff --git a/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-with-output b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-with-output index 43ac53bfe11..6ea910a5bda 100644 --- a/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-with-output +++ b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-with-output @@ -4,5 +4,5 @@ pack: 346574b7331dc3a1724da218d622c6e1b6c66a57 (out/346574b7331dc3a1724da218d622 3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 HEAD symref-target:refs/heads/main ee3c97678e89db4eab7420b04aef51758359f152 refs/heads/dev 3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/tags/annotated tag:feae03400632392a7f38e5b2775f98a439f5eaf5 +feae03400632392a7f38e5b2775f98a439f5eaf5 refs/tags/annotated object:ee3c97678e89db4eab7420b04aef51758359f152 efa596d621559707b2d221f10490959b2decbc6c refs/tags/unannotated \ No newline at end of file diff --git a/tests/snapshots/plumbing/repository/remote/refs/file-v-any b/tests/snapshots/plumbing/repository/remote/refs/file-v-any index 1ff9b7937dc..3b4b200d937 100644 --- a/tests/snapshots/plumbing/repository/remote/refs/file-v-any +++ b/tests/snapshots/plumbing/repository/remote/refs/file-v-any @@ -1,5 +1,5 @@ 3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 HEAD symref-target:refs/heads/main ee3c97678e89db4eab7420b04aef51758359f152 refs/heads/dev 3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/tags/annotated tag:feae03400632392a7f38e5b2775f98a439f5eaf5 +feae03400632392a7f38e5b2775f98a439f5eaf5 refs/tags/annotated object:ee3c97678e89db4eab7420b04aef51758359f152 efa596d621559707b2d221f10490959b2decbc6c refs/tags/unannotated \ No newline at end of file