Skip to content

bare clone for git-repository #551

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 43 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0135532
checkboxes for upcoming bare clone capability
Byron Oct 5, 2022
50fbcca
layout save API for remotes
Byron Oct 5, 2022
1cba50f
thanks clippy
Byron Oct 5, 2022
acb0738
Merge branch 'main' into clone
Byron Oct 7, 2022
e1f7c5f
A basic save_to() implementation for url and push-url
Byron Oct 8, 2022
769e897
improve clarity docs related to mutating sections
Byron Oct 8, 2022
1375368
test another prerequisite
Byron Oct 8, 2022
79a4952
save all data we currently use on a Remote
Byron Oct 8, 2022
e533993
fix: `File::remove_section()` was fixed to allow re-adding a similarl…
Byron Oct 8, 2022
5df2a2a
feat: Add various methods to iterate sections along with their id, an…
Byron Oct 8, 2022
4126d99
remove prior existing values for remotes before saving
Byron Oct 8, 2022
d82a08d
thanks clippy
Byron Oct 8, 2022
ca5bb5e
assure unrelated and unwritten keys are not touched
Byron Oct 8, 2022
9c1e639
fix: greatly improve whitespace handling when removing values.
Byron Oct 8, 2022
058473d
adapt to changes in `git-config`
Byron Oct 8, 2022
884b6e9
add test to assert on lossless saving of existing named remotes
Byron Oct 9, 2022
58a6000
feat: lossless serialization of file urls.
Byron Oct 9, 2022
39ce98b
feat: (mostly) lossless roundtripping of scp-like urls.
Byron Oct 9, 2022
5926322
more assurance we understand how relative paths in scp-like urls work
Byron Oct 9, 2022
a1068a3
adjust to changes in `git-url`
Byron Oct 9, 2022
22d3b37
feat: `Url::try_from(path: &std::path::Path)` for more convenient ins…
Byron Oct 9, 2022
ef1e783
Api sketch to show how clones and bare clones can be done.
Byron Oct 9, 2022
d7f495d
refactor
Byron Oct 9, 2022
615a3a9
sketch a way to configure remotes
Byron Oct 9, 2022
6cbea96
start adding support for naming the remote, but…
Byron Oct 9, 2022
8c8fba2
thanks clippy
Byron Oct 9, 2022
aa5d66f
feat: add `parse::section::header::is_valid_subsection()` function.
Byron Oct 9, 2022
b2c9af1
Another test to validate components must not be empty
Byron Oct 9, 2022
1fb97fb
properly validate remotes when instantiating them and when naming them
Byron Oct 9, 2022
3f8b0e5
make it possible to renmae the remote
Byron Oct 9, 2022
f65a1f4
thanks clippy
Byron Oct 9, 2022
f993cd4
sketch and test for `clone::Prepare::fetch_only()`
Byron Oct 10, 2022
8b804a3
update progress with intended uses of `clone.` variables
Byron Oct 10, 2022
b0f5836
Complete implementation of `fetch_only`, even though configuration is…
Byron Oct 10, 2022
5ecc965
the first successful test for cloning a bare repository
Byron Oct 10, 2022
1a1e862
update usage of clone related configuration
Byron Oct 10, 2022
bc2710f
thanks clippy
Byron Oct 10, 2022
2a0a87a
change!: remove lifetime of `match_group::Fix`, keeping `RefSpec` ins…
Byron Oct 10, 2022
c62f37a
adjust to changes in `git-refspec`
Byron Oct 10, 2022
3865aea
validate the outcome of the fetch as well
Byron Oct 10, 2022
dff66e8
validate default remote name as well and show minimal clone without w…
Byron Oct 10, 2022
314b5e0
fix build
Byron Oct 10, 2022
4474352
fix docs
Byron Oct 10, 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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ Please see _'Development Status'_ for a listing of all crates and their capabili
* **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.
* **fetch** - fetch the current remote or the given one, optionally just as dry-run.
* [x] **fetch** - fetch the current remote or the given one, optionally just as dry-run.
* **clone**
* [ ] initialize a new **bare** repository and fetch all objects.
* [ ] initialize a new repository, fetch all objects and checkout the main worktree.
* **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
2 changes: 1 addition & 1 deletion etc/check-package-size.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ echo "in root: gitoxide CLI"
(enter git-odb && indent cargo diet -n --package-size-limit 120KB)
(enter git-protocol && indent cargo diet -n --package-size-limit 50KB)
(enter git-packetline && indent cargo diet -n --package-size-limit 35KB)
(enter git-repository && indent cargo diet -n --package-size-limit 185KB)
(enter git-repository && indent cargo diet -n --package-size-limit 200KB)
(enter git-transport && indent cargo diet -n --package-size-limit 55KB)
(enter gitoxide-core && indent cargo diet -n --package-size-limit 90KB)
54 changes: 45 additions & 9 deletions git-config/src/file/access/mutate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::borrow::Cow;

use git_features::threading::OwnShared;

use crate::file::SectionBodyIdsLut;
use crate::{
file::{self, rename_section, write::ends_with_newline, MetadataFilter, SectionId, SectionMut},
lookup,
Expand All @@ -11,7 +12,7 @@ use crate::{

/// Mutating low-level access methods.
impl<'event> File<'event> {
/// Returns an mutable section with a given `name` and optional `subsection_name`, _if it exists_.
/// Returns the last mutable section with a given `name` and optional `subsection_name`, _if it exists_.
pub fn section_mut<'a>(
&'a mut self,
name: impl AsRef<str>,
Expand All @@ -29,7 +30,16 @@ impl<'event> File<'event> {
.expect("BUG: Section did not have id from lookup")
.to_mut(nl))
}
/// Returns an mutable section with a given `name` and optional `subsection_name`, _if it exists_, or create a new section.

/// Return the mutable section identified by `id`, or `None` if it didn't exist.
///
/// Note that `id` is stable across deletions and insertions.
pub fn section_mut_by_id<'a>(&'a mut self, id: SectionId) -> Option<SectionMut<'a, 'event>> {
let nl = self.detect_newline_style_smallvec();
self.sections.get_mut(&id).map(|s| s.to_mut(nl))
}

/// Returns the last mutable section with a given `name` and optional `subsection_name`, _if it exists_, or create a new section.
pub fn section_mut_or_create_new<'a>(
&'a mut self,
name: impl AsRef<str>,
Expand Down Expand Up @@ -182,13 +192,39 @@ impl<'event> File<'event> {
.ok()?
.rev()
.next()?;
self.section_order.remove(
self.section_order
.iter()
.position(|v| *v == id)
.expect("known section id"),
);
self.sections.remove(&id)
self.remove_section_by_id(id)
}

/// Remove the section identified by `id` if it exists and return it, or return `None` if no such section was present.
///
/// Note that section ids are unambiguous even in the face of removals and additions of sections.
pub fn remove_section_by_id(&mut self, id: SectionId) -> Option<file::Section<'event>> {
self.section_order
.remove(self.section_order.iter().position(|v| *v == id)?);
let section = self.sections.remove(&id)?;
let lut = self
.section_lookup_tree
.get_mut(&section.header.name)
.expect("lookup cache still has name to be deleted");
for entry in lut {
match section.header.subsection_name.as_deref() {
Some(subsection_name) => {
if let SectionBodyIdsLut::NonTerminal(map) = entry {
if let Some(ids) = map.get_mut(subsection_name) {
ids.remove(ids.iter().position(|v| *v == id).expect("present"));
break;
}
}
}
None => {
if let SectionBodyIdsLut::Terminal(ids) = entry {
ids.remove(ids.iter().position(|v| *v == id).expect("present"));
break;
}
}
}
}
Some(section)
}

/// Removes the section with `name` and `subsection_name` that passed `filter`, returning the removed section
Expand Down
29 changes: 27 additions & 2 deletions git-config/src/file/access/read_only.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::{borrow::Cow, convert::TryFrom, iter::FromIterator};
use std::{borrow::Cow, convert::TryFrom};

use bstr::BStr;
use git_features::threading::OwnShared;
use smallvec::SmallVec;

use crate::file::SectionId;
use crate::{
file,
file::{
Expand Down Expand Up @@ -206,6 +207,25 @@ impl<'event> File<'event> {
})
}

/// Similar to [`sections_by_name()`][Self::sections_by_name()], but returns an identifier for this section as well to allow
/// referring to it unambiguously even in the light of deletions.
#[must_use]
pub fn sections_and_ids_by_name<'a>(
&'a self,
name: &'a str,
) -> Option<impl Iterator<Item = (&file::Section<'event>, SectionId)> + '_> {
self.section_ids_by_name(name).ok().map(move |ids| {
ids.map(move |id| {
(
self.sections
.get(&id)
.expect("section doesn't have id from from lookup"),
id,
)
})
})
}

/// Gets all sections that match the provided `name`, ignoring any subsections, and pass the `filter`.
#[must_use]
pub fn sections_by_name_and_filter<'a>(
Expand Down Expand Up @@ -258,6 +278,11 @@ impl<'event> File<'event> {
self.section_order.iter().map(move |id| &self.sections[id])
}

/// Return an iterator over all sections and their ids, in order of occurrence in the file itself.
pub fn sections_and_ids(&self) -> impl Iterator<Item = (&file::Section<'event>, SectionId)> + '_ {
self.section_order.iter().map(move |id| (&self.sections[id], *id))
}

/// Return an iterator over all sections along with non-section events that are placed right after them,
/// in order of occurrence in the file itself.
///
Expand Down Expand Up @@ -296,6 +321,6 @@ impl<'event> File<'event> {
}

pub(crate) fn detect_newline_style_smallvec(&self) -> SmallVec<[u8; 2]> {
SmallVec::from_iter(self.detect_newline_style().iter().copied())
self.detect_newline_style().as_ref().into()
}
}
2 changes: 1 addition & 1 deletion git-config/src/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl AddAssign<usize> for Size {
/// words, it's possible that a section may have an ID of 3 but the next section
/// has an ID of 5 as 4 was deleted.
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
pub(crate) struct SectionId(pub(crate) usize);
pub struct SectionId(pub(crate) usize);

/// All section body ids referred to by a section name.
///
Expand Down
34 changes: 25 additions & 9 deletions git-config/src/file/mutable/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl<'a, 'event> SectionMut<'a, 'event> {
Some((key_range, value_range)) => {
let value_range = value_range.unwrap_or(key_range.end - 1..key_range.end);
let range_start = value_range.start;
let ret = self.remove_internal(value_range);
let ret = self.remove_internal(value_range, false);
self.section
.body
.0
Expand All @@ -109,7 +109,7 @@ impl<'a, 'event> SectionMut<'a, 'event> {
pub fn remove(&mut self, key: impl AsRef<str>) -> Option<Cow<'event, BStr>> {
let key = Key::from_str_unchecked(key.as_ref());
let (key_range, _value_range) = self.key_and_value_range_by(&key)?;
Some(self.remove_internal(key_range))
Some(self.remove_internal(key_range, true))
}

/// Adds a new line event. Note that you don't need to call this unless
Expand Down Expand Up @@ -231,17 +231,33 @@ impl<'a, 'event> SectionMut<'a, 'event> {
}

/// Performs the removal, assuming the range is valid.
fn remove_internal(&mut self, range: Range<usize>) -> Cow<'event, BStr> {
self.section
.body
.0
.drain(range)
.fold(Cow::Owned(BString::default()), |mut acc, e| {
fn remove_internal(&mut self, range: Range<usize>, fix_whitespace: bool) -> Cow<'event, BStr> {
let events = &mut self.section.body.0;
if fix_whitespace
&& events
.get(range.end)
.map_or(false, |ev| matches!(ev, Event::Newline(_)))
{
events.remove(range.end);
}
let value = events
.drain(range.clone())
.fold(Cow::Owned(BString::default()), |mut acc: Cow<'_, BStr>, e| {
if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e {
acc.to_mut().extend(&**v);
}
acc
})
});
if fix_whitespace
&& range
.start
.checked_sub(1)
.and_then(|pos| events.get(pos))
.map_or(false, |ev| matches!(ev, Event::Whitespace(_)))
{
events.remove(range.start - 1);
}
value
}
}

Expand Down
26 changes: 24 additions & 2 deletions git-config/src/parse/section/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ impl<'a> Header<'a> {
}
}

/// Return true if `name` is valid as subsection name, like `origin` in `[remote "origin"]`.
pub fn is_valid_subsection(name: &BStr) -> bool {
name.find_byteset(b"\n\0").is_none()
}

fn validated_subsection(name: Cow<'_, BStr>) -> Result<Cow<'_, BStr>, Error> {
name.find_byteset(b"\n\0")
.is_none()
is_valid_subsection(name.as_ref())
.then(|| name)
.ok_or(Error::InvalidSubSection)
}
Expand All @@ -55,6 +59,24 @@ fn validated_name(name: Cow<'_, BStr>) -> Result<Cow<'_, BStr>, Error> {
.ok_or(Error::InvalidName)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn empty_header_names_are_legal() {
assert!(Header::new("", None).is_ok(), "yes, git allows this, so do we");
}

#[test]
fn empty_header_sub_names_are_legal() {
assert!(
Header::new("remote", Some("".into())).is_ok(),
"yes, git allows this, so do we"
);
}
}

impl Header<'_> {
///Return true if this is a header like `[legacy.subsection]`, or false otherwise.
pub fn is_legacy(&self) -> bool {
Expand Down
4 changes: 2 additions & 2 deletions git-config/src/parse/section/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,10 @@ mod types {
generate_case_insensitive!(
Name,
name,
"Valid names consist alphanumeric characters or dashes.",
"Valid names consist of alphanumeric characters or dashes.",
is_valid_name,
bstr::BStr,
"Wrapper struct for section header names, like `includeIf`, since these are case-insensitive."
"Wrapper struct for section header names, like `remote`, since these are case-insensitive."
);

generate_case_insensitive!(
Expand Down
48 changes: 48 additions & 0 deletions git-config/tests/file/access/mutate.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,51 @@
mod remove_section {
use std::convert::TryFrom;

#[test]
fn removal_of_all_sections_programmatically_with_sections_and_ids_by_name() {
let mut file = git_config::File::try_from("[core] \na = b\nb=c\n\n[core \"name\"]\nd = 1\ne = 2").unwrap();
for id in file
.sections_and_ids_by_name("core")
.expect("2 sections present")
.map(|(_, id)| id)
.collect::<Vec<_>>()
{
file.remove_section_by_id(id);
}
assert!(file.is_void());
assert_eq!(file.sections().count(), 0);
}

#[test]
fn removal_of_all_sections_programmatically_with_sections_and_ids() {
let mut file = git_config::File::try_from("[core] \na = b\nb=c\n\n[core \"name\"]\nd = 1\ne = 2").unwrap();
for id in file.sections_and_ids().map(|(_, id)| id).collect::<Vec<_>>() {
file.remove_section_by_id(id);
}
assert!(file.is_void());
assert_eq!(file.sections().count(), 0);
}

#[test]
fn removal_is_complete_and_sections_can_be_readded() {
let mut file = git_config::File::try_from("[core] \na = b\nb=c\n\n[core \"name\"]\nd = 1\ne = 2").unwrap();
assert_eq!(file.sections().count(), 2);

let removed = file.remove_section("core", None).expect("removed correct section");
assert_eq!(removed.header().name(), "core");
assert_eq!(removed.header().subsection_name(), None);
assert_eq!(file.sections().count(), 1);

let removed = file.remove_section("core", Some("name")).expect("found");
assert_eq!(removed.header().name(), "core");
assert_eq!(removed.header().subsection_name().expect("present"), "name");
assert_eq!(file.sections().count(), 0);

file.section_mut_or_create_new("core", None).expect("creation succeeds");
file.section_mut_or_create_new("core", Some("name"))
.expect("creation succeeds");
}
}
mod rename_section {
use std::{borrow::Cow, convert::TryFrom};

Expand Down
14 changes: 10 additions & 4 deletions git-config/tests/file/mutable/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ fn section_mut_or_create_new_filter_may_reject_existing_sections() -> crate::Res
Ok(())
}

#[test]
fn section_mut_by_id() {
let mut config = multi_value_section();
let id = config.sections_and_ids().next().expect("at least one").1;
let section = config.section_mut_by_id(id).expect("present");
assert_eq!(section.header().name(), "a");
assert_eq!(section.header().subsection_name(), None);
}

mod remove {
use super::multi_value_section;

Expand All @@ -49,10 +58,7 @@ mod remove {
}

assert!(!section.is_void(), "everything is still there");
assert_eq!(
config.to_string(),
"\n [a]\n \n \n \n \n "
);
assert_eq!(config.to_string(), "\n [a]\n");
Ok(())
}
}
Expand Down
Loading