Skip to content

Commit def53b3

Browse files
committed
Merge branch 'gix-clone'
2 parents 2514334 + 3890f1a commit def53b3

File tree

115 files changed

+3642
-1300
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+3642
-1300
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ jobs:
3838
- name: Setup dependencies
3939
run:
4040
sudo apt-get install tree
41-
- run: git config --global protocol.file.allow always # workaround for https://bugs.launchpad.net/ubuntu/+source/git/+bug/1993586
4241
- name: test
4342
env:
4443
CI: true

Cargo.lock

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ git-repository = { version = "^0.25.0", path = "git-repository", default-feature
8686
git-transport-for-configuration-only = { package = "git-transport", optional = true, version = "^0.21.0", path = "git-transport" }
8787

8888
clap = { version = "3.2.5", features = ["derive", "cargo"] }
89-
prodash = { version = "20.2.0", optional = true, default-features = false }
89+
prodash = { version = "21", optional = true, default-features = false }
9090
atty = { version = "0.2.14", optional = true, default-features = false }
9191
env_logger = { version = "0.9.0", default-features = false }
9292
crosstermion = { version = "0.10.1", optional = true, default-features = false }

DEVELOPMENT.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,9 @@ A bunch of notes collected to keep track of what's needed to eventually support
125125

126126
## `Options` vs `Context`
127127

128-
- Use `Options` whenever there is something to configure in terms of branching behaviour.
129-
- Use `Context` when potential optional data is required to perform an operation at all. See `git_config::path::Context` as reference.
128+
- Use `Options` whenever there is something to configure in terms of branching behaviour. It can be defaulted, and if it can't these fields should be parameters.
129+
- Use `Context` when potential optional data is required to perform an operation at all. See `git_config::path::Context` as reference. It can't be defaulted and the
130+
fields could also be parameters.
130131

131132
## Examples, Experiments, Porcelain CLI and Plumbing CLI - which does what?
132133

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ Please see _'Development Status'_ for a listing of all crates and their capabili
5454
* [x] **ref-map** - show how remote references relate to their local tracking branches as mapped by refspecs.
5555
* [x] **fetch** - fetch the current remote or the given one, optionally just as dry-run.
5656
* **clone**
57-
* [ ] initialize a new **bare** repository and fetch all objects.
58-
* [ ] initialize a new repository, fetch all objects and checkout the main worktree.
57+
* [x] initialize a new **bare** repository and fetch all objects.
58+
* [x] initialize a new repository, fetch all objects and checkout the main worktree.
5959
* **credential**
6060
* [x] **fill/approve/reject** - The same as `git credential`, but implemented in Rust, calling helpers only when from trusted configuration.
6161
* **free** - no git repository necessary
@@ -313,11 +313,13 @@ For additional details, also take a look at the [collaboration guide].
313313
Provide a CLI to for the most basic user journey:
314314

315315
* [x] initialize a repository
316+
* [x] fetch
317+
* [ ] and update worktree
316318
* clone a repository
317319
- [ ] bare
318320
- [ ] with working tree
319-
* [ ] create a commit
320-
* [ ] add a remote
321+
* [ ] create a commit after adding worktree files
322+
* [x] add a remote
321323
* [ ] push
322324
* [x] create (thin) pack
323325

crate-status.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Check out the [performance discussion][git-diff-performance] as well.
110110
* There are various ways to generate a patch from two blobs.
111111
* [ ] any
112112
* **lines**
113-
* [ ] Simple line-by-line diffs powered by the `similar` crate.
113+
* [x] Simple line-by-line diffs powered by the `imara-diff` crate.
114114
* diffing, merging, working with hunks of data
115115
* find differences between various states, i.e. index, working tree, commit-tree
116116
* [x] API documentation
@@ -143,8 +143,6 @@ Check out the [performance discussion][git-traverse-performance] as well.
143143
* [x] convert URL to string
144144
* [x] API documentation
145145
* [ ] Some examples
146-
- **deviation**
147-
* URLs may not contain passwords, which cannot be represent here and if present, will be ignored.
148146

149147
### git-protocol
150148
* _abstract over protocol versions to allow delegates to deal only with a single way of doing things_
@@ -467,7 +465,7 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README.
467465
* [x] access to refs and objects
468466
* **credentials**
469467
* [x] run `git credential` directly
470-
* [x] use credential helper configuration and to obtain credentials with `git_credential::helper::Cascade`
468+
* [x] use credential helper configuration and to obtain credentials with `git_credentials::helper::Cascade`
471469
* **config**
472470
* [ ] facilities to apply the [url-match](https://git-scm.com/docs/git-config#Documentation/git-config.txt-httplturlgt) algorithm and to
473471
[normalize urls](https://github.com/git/git/blob/be1a02a17ede4082a86dfbfee0f54f345e8b43ac/urlmatch.c#L109:L109) before comparison.
@@ -504,11 +502,12 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README.
504502
* **remotes**
505503
* [ ] clone
506504
* [ ] shallow
507-
* [ ] fetch
505+
* [ ] [bundles](https://git-scm.com/docs/git-bundle)
506+
* [x] fetch
508507
* [ ] push
509508
* [x] ls-refs
510-
* [ ] ls-refs with ref-spec filter
511-
* [ ] list, find by name
509+
* [x] ls-refs with ref-spec filter
510+
* [x] list, find by name
512511
* [x] create in memory
513512
* [ ] groups
514513
* [ ] [remote and branch files](https://github.com/git/git/blob/master/remote.c#L300)

etc/check-package-size.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ echo "in root: gitoxide CLI"
5757
(enter git-odb && indent cargo diet -n --package-size-limit 120KB)
5858
(enter git-protocol && indent cargo diet -n --package-size-limit 55KB)
5959
(enter git-packetline && indent cargo diet -n --package-size-limit 35KB)
60-
(enter git-repository && indent cargo diet -n --package-size-limit 200KB)
60+
(enter git-repository && indent cargo diet -n --package-size-limit 210KB)
6161
(enter git-transport && indent cargo diet -n --package-size-limit 60KB)
6262
(enter gitoxide-core && indent cargo diet -n --package-size-limit 90KB)

git-config/src/file/access/comfort.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::TryFrom};
22

33
use bstr::BStr;
44

5-
use crate::{file::MetadataFilter, lookup, parse::section, value, File};
5+
use crate::{file::MetadataFilter, value, File};
66

77
/// Comfortable API for accessing values
88
impl<'event> File<'event> {
@@ -81,19 +81,22 @@ impl<'event> File<'event> {
8181
filter: &mut MetadataFilter,
8282
) -> Option<Result<bool, value::Error>> {
8383
let section_name = section_name.as_ref();
84+
let section_ids = self
85+
.section_ids_by_name_and_subname(section_name, subsection_name)
86+
.ok()?;
8487
let key = key.as_ref();
85-
match self.raw_value_filter(section_name, subsection_name, key, filter) {
86-
Ok(v) => Some(crate::Boolean::try_from(v).map(|b| b.into())),
87-
Err(lookup::existing::Error::KeyMissing) => {
88-
let section = self
89-
.section_filter(section_name, subsection_name, filter)
90-
.ok()
91-
.flatten()?;
92-
let key = section::Key::try_from(key).ok()?;
93-
section.key_and_value_range_by(&key).map(|_| Ok(true))
88+
for section_id in section_ids.rev() {
89+
let section = self.sections.get(&section_id).expect("known section id");
90+
if !filter(section.meta()) {
91+
continue;
92+
}
93+
match section.value_implicit(key) {
94+
Some(Some(v)) => return Some(crate::Boolean::try_from(v).map(|b| b.into())),
95+
Some(None) => return Some(Ok(true)),
96+
None => continue,
9497
}
95-
Err(_err) => None,
9698
}
99+
None
97100
}
98101

99102
/// Like [`value()`][File::value()], but returning an `Option` if the integer wasn't found.

git-config/src/file/mutable/section.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ impl<'a, 'event> SectionMut<'a, 'event> {
3737
}
3838

3939
body.push(Event::SectionKey(key));
40-
if let Some(value) = value {
41-
body.extend(self.whitespace.key_value_separators());
42-
body.push(Event::Value(escape_value(value).into()));
40+
match value {
41+
Some(value) => {
42+
body.extend(self.whitespace.key_value_separators());
43+
body.push(Event::Value(escape_value(value).into()));
44+
}
45+
None => body.push(Event::Value(Cow::Borrowed("".into()))),
4346
}
4447
if self.implicit_newline {
4548
body.push(Event::Newline(BString::from(self.newline.to_vec()).into()));

git-config/src/file/section/body.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,32 @@ impl<'event> Body<'event> {
1818
/// Note that we consider values without key separator `=` non-existing.
1919
#[must_use]
2020
pub fn value(&self, key: impl AsRef<str>) -> Option<Cow<'_, BStr>> {
21+
self.value_implicit(key).flatten()
22+
}
23+
24+
/// Retrieves the last matching value in a section with the given key, if present, and indicates an implicit value with `Some(None)`,
25+
/// and a non-existing one as `None`
26+
#[must_use]
27+
pub fn value_implicit(&self, key: impl AsRef<str>) -> Option<Option<Cow<'_, BStr>>> {
2128
let key = Key::from_str_unchecked(key.as_ref());
2229
let (_key_range, range) = self.key_and_value_range_by(&key)?;
23-
let range = range?;
30+
let range = match range {
31+
None => return Some(None),
32+
Some(range) => range,
33+
};
2434
let mut concatenated = BString::default();
2535

2636
for event in &self.0[range] {
2737
match event {
2838
Event::Value(v) => {
29-
return Some(normalize_bstr(v.as_ref()));
39+
return Some(Some(normalize_bstr(v.as_ref())));
3040
}
3141
Event::ValueNotDone(v) => {
3242
concatenated.push_str(v.as_ref());
3343
}
3444
Event::ValueDone(v) => {
3545
concatenated.push_str(v.as_ref());
36-
return Some(normalize_bstring(concatenated));
46+
return Some(Some(normalize_bstring(concatenated)));
3747
}
3848
_ => (),
3949
}

git-config/src/file/util.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,6 @@ impl<'event> File<'event> {
124124
.get(&section_name)
125125
.ok_or(lookup::existing::Error::SectionMissing)?;
126126
let mut maybe_ids = None;
127-
// Don't simplify if and matches here -- the for loop currently needs
128-
// `n + 1` checks, while the if and matches will result in the for loop
129-
// needing `2n` checks.
130127
if let Some(subsection_name) = subsection_name {
131128
let subsection_name: &BStr = subsection_name.into();
132129
for node in section_ids {
@@ -152,16 +149,17 @@ impl<'event> File<'event> {
152149
) -> Result<impl Iterator<Item = SectionId> + '_, lookup::existing::Error> {
153150
let section_name = section::Name::from_str_unchecked(section_name);
154151
match self.section_lookup_tree.get(&section_name) {
155-
Some(lookup) => Ok(lookup.iter().flat_map({
156-
let section_order = &self.section_order;
157-
move |node| match node {
158-
SectionBodyIdsLut::Terminal(v) => Box::new(v.iter().copied()) as Box<dyn Iterator<Item = _>>,
159-
SectionBodyIdsLut::NonTerminal(v) => Box::new({
160-
let v: Vec<_> = v.values().flatten().copied().collect();
161-
section_order.iter().filter(move |a| v.contains(a)).copied()
162-
}),
152+
Some(lookup) => {
153+
let mut lut = Vec::with_capacity(self.section_order.len());
154+
for node in lookup {
155+
match node {
156+
SectionBodyIdsLut::Terminal(v) => lut.extend(v.iter().copied()),
157+
SectionBodyIdsLut::NonTerminal(v) => lut.extend(v.values().flatten().copied()),
158+
}
163159
}
164-
})),
160+
161+
Ok(self.section_order.iter().filter(move |a| lut.contains(a)).copied())
162+
}
165163
None => Err(lookup::existing::Error::SectionMissing),
166164
}
167165
}

git-config/src/parse/nom/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ fn config_value<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResul
290290
} else {
291291
// This is a special way of denoting 'empty' values which a lot of code depends on.
292292
// Hence, rather to fix this everywhere else, leave it here and fix it where it matters, namely
293-
// when it's about differentiating between a missing key-vaue separator, and one followed by emptiness.
293+
// when it's about differentiating between a missing key-value separator, and one followed by emptiness.
294294
dispatch(Event::Value(Cow::Borrowed("".into())));
295295
Ok((i, 0))
296296
}

git-config/tests/file/access/read_only.rs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,21 @@ fn get_value_for_all_provided_values() -> crate::Result {
4242

4343
assert!(
4444
config.value::<Boolean>("core", None, "bool-implicit").is_err(),
45-
"this cannot work like in git as the value isn't there for us"
45+
"this cannot work like in git as the original value isn't there for us"
4646
);
4747
assert!(
4848
config.boolean("core", None, "bool-implicit").expect("present")?,
49-
"this should work"
49+
"implicit booleans resolve to being true"
50+
);
51+
assert_eq!(
52+
config.string("core", None, "bool-implicit"),
53+
None,
54+
"unset values are not present"
55+
);
56+
assert_eq!(
57+
config.strings("core", None, "bool-implicit").expect("present"),
58+
&[cow_str("")],
59+
"unset values show up as empty within a string array"
5060
);
5161

5262
assert_eq!(config.string("doesnt", None, "exist"), None);
@@ -146,8 +156,6 @@ fn get_value_for_all_provided_values() -> crate::Result {
146156
Ok(())
147157
}
148158

149-
/// There was a regression where lookup would fail because we only checked the
150-
/// last section entry for any given section and subsection
151159
#[test]
152160
fn get_value_looks_up_all_sections_before_failing() -> crate::Result {
153161
let config = r#"
@@ -163,14 +171,17 @@ fn get_value_looks_up_all_sections_before_failing() -> crate::Result {
163171
// Checks that we check the last entry first still
164172
assert!(
165173
!file.value::<Boolean>("core", None, "bool-implicit")?.0,
166-
"this one can't do it, needs special handling"
174+
"implicit bool is invisible to `value` and boolean is the only value we want. Would have to special case it."
167175
);
168176
assert!(
169-
!file.boolean("core", None, "bool-implicit").expect("present")?,
170-
"this should work, but doesn't yet"
177+
file.boolean("core", None, "bool-implicit").expect("present")?,
178+
"correct handling of booleans is implemented specifically"
171179
);
172180

173-
assert!(!file.value::<Boolean>("core", None, "bool-explicit")?.0);
181+
assert!(
182+
!file.value::<Boolean>("core", None, "bool-explicit")?.0,
183+
"explicit values always work"
184+
);
174185

175186
Ok(())
176187
}
@@ -313,3 +324,34 @@ fn multi_line_value_outer_quotes_escaped_inner_quotes() {
313324
let expected = r#"!f() { git status; git add -A; git commit -m "$1"; git push -f; git log -1; }; f; unset f"#;
314325
assert_eq!(config.raw_value("alias", None, "save").unwrap().as_ref(), expected);
315326
}
327+
328+
#[test]
329+
fn overrides_with_implicit_booleans_work_in_single_section() {
330+
let config = r#"
331+
[a]
332+
b = false
333+
b
334+
"#;
335+
let config = File::try_from(config).unwrap();
336+
assert_eq!(
337+
config.boolean("a", None, "b"),
338+
Some(Ok(true)),
339+
"empty implicit booleans "
340+
);
341+
}
342+
343+
#[test]
344+
fn overrides_with_implicit_booleans_work_across_sections() {
345+
let config = r#"
346+
[a]
347+
b = false
348+
[a]
349+
b
350+
"#;
351+
let config = File::try_from(config).unwrap();
352+
assert_eq!(
353+
config.boolean("a", None, "b"),
354+
Some(Ok(true)),
355+
"empty implicit booleans "
356+
);
357+
}

git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::{
99
use bstr::{BString, ByteSlice};
1010
use git_config::file::init::{self};
1111

12+
use crate::file::init::from_paths::includes::conditional::git_init;
1213
use crate::file::{
1314
cow_str,
1415
init::from_paths::{escape_backslashes, includes::conditional::options_with_git_dir},
@@ -216,7 +217,7 @@ fn write_main_config(
216217
env: &GitEnv,
217218
overwrite_config_location: ConfigLocation,
218219
) -> crate::Result {
219-
git_repository::init(env.worktree_dir())?;
220+
git_init(env.worktree_dir(), false)?;
220221

221222
if overwrite_config_location == ConfigLocation::Repo {
222223
write_append_config_value(env.git_dir().join("config"), "base-value")?;

git-config/tests/file/init/from_paths/includes/conditional/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use git_config::{
44
file::{includes, init},
55
path, File,
66
};
7+
use git_repository as git;
78
use tempfile::tempdir;
89

910
use crate::file::{cow_str, init::from_paths::escape_backslashes};
@@ -138,6 +139,17 @@ fn options_with_git_dir(git_dir: &Path) -> init::Options<'_> {
138139
}
139140
}
140141

142+
fn git_init(path: impl AsRef<std::path::Path>, bare: bool) -> crate::Result<git::Repository> {
143+
Ok(git::ThreadSafeRepository::init_opts(
144+
path,
145+
bare.then(|| git::create::Kind::Bare)
146+
.unwrap_or(git::create::Kind::WithWorktree),
147+
git::create::Options::default(),
148+
git::open::Options::isolated(),
149+
)?
150+
.to_thread_local())
151+
}
152+
141153
fn create_symlink(from: impl AsRef<Path>, to: impl AsRef<Path>) {
142154
std::fs::create_dir_all(from.as_ref().parent().unwrap()).unwrap();
143155
#[cfg(not(windows))]

0 commit comments

Comments
 (0)