Skip to content

Commit

Permalink
Merge branch 'filter-refs'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Sep 18, 2022
2 parents fd14489 + 461ff27 commit 3773b92
Show file tree
Hide file tree
Showing 39 changed files with 623 additions and 300 deletions.
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

0 comments on commit 3773b92

Please sign in to comment.