Skip to content

filter refspecs in git-repository #534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Sep 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5d5e211
sketch API for obtaining a reflist filtered by refspecs (#450)
Byron Sep 16, 2022
6d1c372
A more concrete sketch of how the API for obtaining mappings should l…
Byron Sep 17, 2022
21420da
fix `match_group::Item` to make it uniform with how we typically name…
Byron Sep 17, 2022
bab5860
change!: rename `fetch::Ref::path` to `fetch::Ref::full_ref_name`. (#…
Byron Sep 17, 2022
d2c772e
change!: `fetch::Ref::unpack()` now returns the peeled object as well…
Byron Sep 17, 2022
06d45ff
adjust to changes in `git-protocol` (#450)
Byron Sep 17, 2022
91f193f
change!: `fetch::Ref::unpack()` returns `&BStr` instead of `&BString`…
Byron Sep 17, 2022
777ba7f
A step closer to actually obtaining a validated ref-mapping. (#450)
Byron Sep 17, 2022
7e96d18
feat: obtain a refmap after listing refs via `remote::Connection::lis…
Byron Sep 17, 2022
52fa247
thanks clippy
Byron Sep 17, 2022
85c49ec
Allow `match_group::Fix` to be cloned. (#450)
Byron Sep 17, 2022
ffa24a1
adjust to changes in `git-refspec` (#450)
Byron Sep 17, 2022
3958d71
Provide all information generated by the handshake into the refmap re…
Byron Sep 17, 2022
08fd1e4
list `git-refspec` crate as well (#450)
Byron Sep 17, 2022
2a7df32
Make `specs` in `MatchGroup` public to reduce API surface. (#450)
Byron Sep 17, 2022
910cedc
also provide the spec-index with the returned refmap. (#450)
Byron Sep 17, 2022
27fb1ce
change!: Add `Kind::GitInstallation` for a way to obtain special git-…
Byron Sep 18, 2022
0f9833a
adjust to changes in `git-config` (#450)
Byron Sep 18, 2022
c9ff885
produce only a ref-map as it contains all data somebody would want. (…
Byron Sep 18, 2022
94c2b78
wire up the `ref-map` sub-command. (#450)
Byron Sep 18, 2022
4dbfa4c
first basic implementation of `ref-map` (#450)
Byron Sep 18, 2022
6f60a79
better refmap printing (#450)
Byron Sep 18, 2022
d8f1608
Add method to allow replacing a Remote's refspecs entirely. (#450)
Byron Sep 18, 2022
f4d8198
Correct printing of tag information (even though it doesn't look grea…
Byron Sep 18, 2022
2237495
show fixes as well (#450)
Byron Sep 18, 2022
e819fc6
A more efficient representation for `validate::Fix` (#450)
Byron Sep 18, 2022
91d1a3a
adapt to changes in `git-refspec` (#450)
Byron Sep 18, 2022
fec6db8
nicer printing of fixes (#450)
Byron Sep 18, 2022
098961f
don't print tags as target, as it's misleading (#450)
Byron Sep 18, 2022
4720666
option to print server information about the connection (#450)
Byron Sep 18, 2022
11851f3
refactor (#450)
Byron Sep 18, 2022
461ff27
fix journeytests (#450)
Byron Sep 18, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -142,6 +143,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)
Expand Down
1 change: 0 additions & 1 deletion crate-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions git-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
63 changes: 62 additions & 1 deletion git-config/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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],
Expand All @@ -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,
Expand All @@ -61,6 +65,7 @@ impl Source {
pub fn storage_location(self, env_var: &mut dyn FnMut(&str) -> Option<OsString>) -> Option<Cow<'static, Path>> {
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()),
Expand Down Expand Up @@ -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<Option<BString>> = 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)
);
}
}
}
}
2 changes: 2 additions & 0 deletions git-config/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions git-protocol/src/fetch/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use git_transport::client::Capabilities;
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,
Expand Down
33 changes: 20 additions & 13 deletions git-protocol/src/fetch/refs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bstr::BString;
use bstr::{BStr, BString};

mod error {
use crate::fetch::refs::parse;
Expand Down Expand Up @@ -50,24 +50,24 @@ 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.
object: git_hash::ObjectId,
},
/// 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
Expand All @@ -78,13 +78,20 @@ 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, [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) -> (&BStr, &git_hash::oid, Option<&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::Symbolic {
full_ref_name, object, ..
} => (full_ref_name.as_ref(), object, None),
Ref::Peeled {
full_ref_name,
tag: object,
object: peeled,
} => (full_ref_name.as_ref(), object, Some(peeled)),
}
}
}
Expand Down
30 changes: 22 additions & 8 deletions git-protocol/src/fetch/refs/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,28 @@ impl From<InternalRef> 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")
}
Expand Down Expand Up @@ -170,17 +184,17 @@ pub(in crate::fetch::refs) fn parse_v2(line: &str) -> Result<Ref, Error> {
}
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(),
},
Expand All @@ -198,7 +212,7 @@ pub(in crate::fetch::refs) fn parse_v2(line: &str) -> Result<Ref, Error> {
} else {
Ref::Direct {
object: id,
path: path.into(),
full_ref_name: path.into(),
}
})
}
Expand Down
20 changes: 10 additions & 10 deletions git-protocol/src/fetch/tests/refs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
},
]
Expand Down Expand Up @@ -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")
},
Expand Down
4 changes: 2 additions & 2 deletions git-protocol/tests/fetch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
Expand Down Expand Up @@ -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,
});
}
Expand Down
4 changes: 2 additions & 2 deletions git-protocol/tests/fetch/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
]
Expand Down
6 changes: 3 additions & 3 deletions git-protocol/tests/fetch/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
]
Expand Down Expand Up @@ -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"),
}]
);
Expand Down
Loading