Skip to content

Commit

Permalink
Merge branch 'gix-index-from-tree'
Browse files Browse the repository at this point in the history
Conflicts:
	git-index/src/init.rs
	gitoxide-core/src/repository/mod.rs
	src/plumbing/main.rs
	src/plumbing/options.rs
  • Loading branch information
Byron committed Oct 12, 2022
2 parents 2de0be3 + bae4589 commit 8c24386
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 116 deletions.
232 changes: 119 additions & 113 deletions git-index/src/init.rs
Original file line number Diff line number Diff line change
@@ -1,130 +1,136 @@
use std::collections::VecDeque;

use bstr::{BStr, BString, ByteSlice, ByteVec};
use git_object::{
tree::{self, EntryMode},
TreeRefIter,
};
use git_traverse::tree::{breadthfirst, visit::Action, Visit};

use crate::{
entry::{Flags, Mode, Stat},
Entry, PathStorage, State, Version,
};

/// initialization
impl State {
/// Takes in an oid of a tree object and creates and returns a [`State`][crate::State] from its children.
pub fn from_tree<Find>(tree: &git_hash::oid, mut find: Find) -> Result<Self, breadthfirst::Error>
where
Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>,
{
let mut buf = Vec::new();
let root = find(tree, &mut buf).ok_or(breadthfirst::Error::NotFound { oid: tree.into() })?;
let state = breadthfirst::State::default();
let mut delegate = EntryBuilder::new();
breadthfirst(root, state, &mut find, &mut delegate)?;

Ok(State {
timestamp: filetime::FileTime::now(),
version: Version::V2,
entries: delegate.entries,
path_backing: delegate.path_backing,
is_sparse: false,
tree: None,
link: None,
resolve_undo: None,
untracked: None,
fs_monitor: None,
})
mod from_tree {
use crate::{
entry::{Flags, Mode, Stat},
Entry, PathStorage, State, Version,
};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use git_object::{
tree::{self, EntryMode},
TreeRefIter,
};
use git_traverse::tree::{breadthfirst, visit::Action, Visit};
use std::collections::VecDeque;

/// Initialization
impl State {
/// Create an index [`State`][crate::State] by traversing `tree` recursively, accessing sub-trees
/// with `find`.
///
/// **No extension data is currently produced**.
pub fn from_tree<Find>(tree: &git_hash::oid, mut find: Find) -> Result<Self, breadthfirst::Error>
where
Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>,
{
let mut buf = Vec::new();
let root = find(tree, &mut buf).ok_or(breadthfirst::Error::NotFound { oid: tree.into() })?;

let mut delegate = CollectEntries::new();
breadthfirst(root, breadthfirst::State::default(), &mut find, &mut delegate)?;

let CollectEntries {
mut entries,
path_backing,
path: _,
path_deque: _,
} = delegate;

entries.sort_by(|a, b| Entry::cmp_filepaths(a.path_in(&path_backing), b.path_in(&path_backing)));

Ok(State {
timestamp: filetime::FileTime::now(),
version: Version::V2,
entries,
path_backing,
is_sparse: false,
tree: None,
link: None,
resolve_undo: None,
untracked: None,
fs_monitor: None,
})
}
}
}

struct EntryBuilder {
entries: Vec<Entry>,
path_backing: PathStorage,
path: BString,
path_deque: VecDeque<BString>,
}
struct CollectEntries {
entries: Vec<Entry>,
path_backing: PathStorage,
path: BString,
path_deque: VecDeque<BString>,
}

impl EntryBuilder {
pub fn new() -> EntryBuilder {
EntryBuilder {
entries: Vec::new(),
path_backing: Vec::new(),
path: BString::default(),
path_deque: VecDeque::new(),
impl CollectEntries {
pub fn new() -> CollectEntries {
CollectEntries {
entries: Vec::new(),
path_backing: Vec::new(),
path: BString::default(),
path_deque: VecDeque::new(),
}
}
}

fn push_element(&mut self, name: &BStr) {
if !self.path.is_empty() {
self.path.push(b'/');
fn push_element(&mut self, name: &BStr) {
if !self.path.is_empty() {
self.path.push(b'/');
}
self.path.push_str(name);
}
self.path.push_str(name);
}

pub fn add_entry(&mut self, entry: &tree::EntryRef<'_>) {
let mode = match entry.mode {
EntryMode::Tree => unreachable!("visit_non_tree() called us"),
EntryMode::Blob => Mode::FILE,
EntryMode::BlobExecutable => Mode::FILE_EXECUTABLE,
EntryMode::Link => Mode::SYMLINK,
EntryMode::Commit => Mode::COMMIT,
};

let path_start = self.path_backing.len();
self.path_backing.extend_from_slice(&self.path);

let new_entry = Entry {
stat: Stat::default(),
id: entry.oid.into(),
flags: Flags::empty(),
mode,
path: path_start..self.path_backing.len(),
};

match self
.entries
.binary_search_by(|entry| Entry::cmp_filepaths(entry.path_in(&self.path_backing), self.path.as_bstr()))
{
Ok(pos) => self.entries[pos] = new_entry,
Err(pos) => self.entries.insert(pos, new_entry),
};
pub fn add_entry(&mut self, entry: &tree::EntryRef<'_>) {
let mode = match entry.mode {
EntryMode::Tree => unreachable!("visit_non_tree() called us"),
EntryMode::Blob => Mode::FILE,
EntryMode::BlobExecutable => Mode::FILE_EXECUTABLE,
EntryMode::Link => Mode::SYMLINK,
EntryMode::Commit => Mode::COMMIT,
};

let path_start = self.path_backing.len();
self.path_backing.extend_from_slice(&self.path);

let new_entry = Entry {
stat: Stat::default(),
id: entry.oid.into(),
flags: Flags::empty(),
mode,
path: path_start..self.path_backing.len(),
};

self.entries.push(new_entry);
}
}
}

impl Visit for EntryBuilder {
fn pop_front_tracked_path_and_set_current(&mut self) {
self.path = self
.path_deque
.pop_front()
.expect("every call is matched with push_tracked_path_component");
}
impl Visit for CollectEntries {
fn pop_front_tracked_path_and_set_current(&mut self) {
self.path = self
.path_deque
.pop_front()
.expect("every call is matched with push_tracked_path_component");
}

fn push_back_tracked_path_component(&mut self, component: &bstr::BStr) {
self.push_element(component);
self.path_deque.push_back(self.path.clone());
}
fn push_back_tracked_path_component(&mut self, component: &bstr::BStr) {
self.push_element(component);
self.path_deque.push_back(self.path.clone());
}

fn push_path_component(&mut self, component: &bstr::BStr) {
self.push_element(component);
}
fn push_path_component(&mut self, component: &bstr::BStr) {
self.push_element(component);
}

fn pop_path_component(&mut self) {
if let Some(pos) = self.path.rfind_byte(b'/') {
self.path.resize(pos, 0);
} else {
self.path.clear();
fn pop_path_component(&mut self) {
if let Some(pos) = self.path.rfind_byte(b'/') {
self.path.resize(pos, 0);
} else {
self.path.clear();
}
}
}

fn visit_tree(&mut self, _entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
Action::Continue
}
fn visit_tree(&mut self, _entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
Action::Continue
}

fn visit_nontree(&mut self, entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
self.add_entry(entry);
Action::Continue
fn visit_nontree(&mut self, entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
self.add_entry(entry);
Action::Continue
}
}
}
3 changes: 1 addition & 2 deletions gitoxide-core/src/index/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::path::Path;

use git_repository as git;
use std::path::Path;

pub struct Options {
pub object_hash: git::hash::Kind,
Expand Down
41 changes: 41 additions & 0 deletions gitoxide-core/src/repository/index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use git::prelude::FindExt;
use git_repository as git;
use std::ffi::OsString;
use std::{io::BufWriter, path::PathBuf};

pub fn from_tree(
mut spec: OsString,
index_path: Option<PathBuf>,
force: bool,
repo: git::Repository,
mut out: impl std::io::Write,
) -> anyhow::Result<()> {
spec.push("^{tree}");
let spec = git::path::os_str_into_bstr(&spec)?;
let tree = repo.rev_parse_single(spec)?;
let state = git::index::State::from_tree(&tree, |oid, buf| repo.objects.find_tree_iter(oid, buf).ok())?;
let options = git::index::write::Options {
hash_kind: repo.object_hash(),
..Default::default()
};

match index_path {
Some(index_path) => {
if index_path.is_file() {
if !force {
anyhow::bail!(
"File at \"{}\" already exists, to overwrite use the '-f' flag",
index_path.display()
);
}
}
let writer = BufWriter::new(std::fs::File::create(&index_path)?);
state.write_to(writer, options)?;
}
None => {
state.write_to(&mut out, options)?;
}
}

Ok(())
}
1 change: 1 addition & 0 deletions gitoxide-core/src/repository/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod exclude;
pub mod fetch;
#[cfg(feature = "blocking-client")]
pub use fetch::function::fetch;
pub mod index;
pub mod mailmap;
pub mod odb;
pub mod remote;
Expand Down
18 changes: 17 additions & 1 deletion src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use gitoxide_core::pack::verify;

use crate::{
plumbing::{
options::{commit, config, credential, exclude, free, mailmap, odb, revision, tree, Args, Subcommands},
options::{commit, config, credential, exclude, free, index, mailmap, odb, revision, tree, Args, Subcommands},
show_progress,
},
shared::pretty::prepare_and_run,
Expand Down Expand Up @@ -803,6 +803,22 @@ pub fn main() -> Result<()> {
},
),
},
Subcommands::Index(cmd) => match cmd {
index::Subcommands::FromTree {
force,
index_output_path,
spec,
} => prepare_and_run(
"index-from-tree",
verbose,
progress,
progress_keep_open,
None,
move |_progress, out, _err| {
core::repository::index::from_tree(spec, index_output_path, force, repository(Mode::Strict)?, out)
},
),
},
}?;
Ok(())
}
Expand Down
23 changes: 23 additions & 0 deletions src/plumbing/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ pub enum Subcommands {
/// Interact with the exclude files like .gitignore.
#[clap(subcommand)]
Exclude(exclude::Subcommands),
#[clap(subcommand)]
Index(index::Subcommands),
/// Display overall progress of the gitoxide project as seen from the perspective of git-config.
Progress,
Config(config::Platform),
Expand Down Expand Up @@ -331,5 +333,26 @@ pub mod exclude {
}
}

pub mod index {
use std::path::PathBuf;

#[derive(Debug, clap::Subcommand)]
pub enum Subcommands {
/// Create an index from a tree-ish.
#[clap(visible_alias = "read-tree")]
FromTree {
/// Overwrite the specified index file if it already exists.
#[clap(long, short = 'f')]
force: bool,
/// Path to the index file to be written.
/// If none is given it will be written to stdout, avoiding to overwrite the repository index just yet.
#[clap(long, short = 'i')]
index_output_path: Option<PathBuf>,
/// A revspec that points to the to generate the index from.
spec: std::ffi::OsString,
},
}
}

///
pub mod free;

0 comments on commit 8c24386

Please sign in to comment.