Skip to content

Commit 94551a1

Browse files
Rework unapply to use apply of an inverted filter (#725)
Change-Id: unapply-invert
1 parent e856fd3 commit 94551a1

15 files changed

+405
-263
lines changed

src/bin/josh-filter.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ fn run_filter(args: Vec<String>) -> josh::JoshResult<i32> {
154154
}
155155

156156
if args.is_present("print-filter") {
157+
let filterobj = if args.is_present("reverse") {
158+
josh::filter::invert(filterobj)?
159+
} else {
160+
filterobj
161+
};
157162
println!(
158163
"{}",
159164
josh::filter::pretty(filterobj, if args.is_present("file") { 0 } else { 4 })

src/cache.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::*;
22
use std::collections::HashMap;
33

4-
const VERSION: u64 = 10;
4+
const VERSION: u64 = 11;
55

66
lazy_static! {
77
static ref DB: std::sync::Mutex<Option<sled::Db>> = std::sync::Mutex::new(None);

src/filter/mod.rs

Lines changed: 102 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod opt;
55
mod parse;
66
pub mod tree;
77

8+
pub use opt::invert;
89
pub use parse::get_comments;
910
pub use parse::parse;
1011

@@ -241,6 +242,17 @@ pub fn apply_to_commit(
241242
}
242243
}
243244

245+
fn get_workspace<'a>(repo: &'a git2::Repository, tree: &'a git2::Tree<'a>, path: &Path) -> Filter {
246+
let f = parse::parse(&tree::get_blob(repo, &tree, &path.join("workspace.josh")))
247+
.unwrap_or(to_filter(Op::Empty));
248+
249+
if invert(f).is_ok() {
250+
f
251+
} else {
252+
to_filter(Op::Empty)
253+
}
254+
}
255+
244256
pub fn apply_to_commit3(
245257
filter: Filter,
246258
commit: &git2::Commit,
@@ -328,23 +340,15 @@ fn apply_to_commit2(
328340

329341
let normal_parents = some_or!(normal_parents, { return Ok(None) });
330342

331-
let cw = parse::parse(&tree::get_blob(
332-
repo,
333-
&commit.tree()?,
334-
&ws_path.join("workspace.josh"),
335-
))
336-
.unwrap_or(to_filter(Op::Empty));
343+
let cw = get_workspace(repo, &commit.tree()?, &ws_path);
337344

338345
let extra_parents = commit
339346
.parents()
340347
.map(|parent| {
341348
rs_tracing::trace_scoped!("parent", "id": parent.id().to_string());
342-
let pcw = parse::parse(&tree::get_blob(
343-
repo,
344-
&parent.tree().unwrap_or(tree::empty(repo)),
345-
&ws_path.join("workspace.josh"),
346-
))
347-
.unwrap_or(to_filter(Op::Empty));
349+
350+
let pcw =
351+
get_workspace(repo, &parent.tree().unwrap_or(tree::empty(repo)), &ws_path);
348352

349353
apply_to_commit2(
350354
&to_op(opt::optimize(to_filter(Op::Subtract(cw, pcw)))),
@@ -415,7 +419,7 @@ fn apply_to_commit2(
415419
.unwrap_or(tree::empty_id())
416420
};
417421
let bf = repo.find_tree(bf)?;
418-
let bu = unapply(transaction, *b, bf, tree::empty(repo))?;
422+
let bu = apply(transaction, opt::invert(*b)?, bf)?;
419423
let ba = apply(transaction, *a, bu)?.id();
420424
repo.find_tree(tree::subtract(transaction, af, ba)?)?
421425
}
@@ -515,7 +519,7 @@ fn apply2<'a>(
515519
Op::Subtract(a, b) => {
516520
let af = apply(transaction, *a, tree.clone())?;
517521
let bf = apply(transaction, *b, tree.clone())?;
518-
let bu = unapply(transaction, *b, bf, tree::empty(repo))?;
522+
let bu = apply(transaction, opt::invert(*b)?, bf)?;
519523
let ba = apply(transaction, *a, bu)?.id();
520524
Ok(repo.find_tree(tree::subtract(transaction, af.id(), ba)?)?)
521525
}
@@ -532,12 +536,11 @@ fn apply2<'a>(
532536

533537
Op::Workspace(path) => {
534538
let base = to_filter(Op::Subdir(path.to_owned()));
535-
if let Ok(cw) = parse::parse(&tree::get_blob(repo, &tree, &path.join("workspace.josh")))
536-
{
537-
apply(transaction, compose(base, cw), tree)
538-
} else {
539-
apply(transaction, base, tree)
540-
}
539+
apply(
540+
transaction,
541+
compose(get_workspace(repo, &tree, &path), base),
542+
tree,
543+
)
541544
}
542545

543546
Op::Compose(filters) => {
@@ -563,176 +566,104 @@ pub fn unapply<'a>(
563566
tree: git2::Tree<'a>,
564567
parent_tree: git2::Tree<'a>,
565568
) -> JoshResult<git2::Tree<'a>> {
566-
unapply2(transaction, &to_op(filter), tree, parent_tree)
569+
if let Ok(inverted) = opt::invert(filter) {
570+
let matching = apply(transaction, chain(filter, inverted), parent_tree.clone())?;
571+
let stripped = tree::subtract(transaction, parent_tree.id(), matching.id())?;
572+
let new_tree = apply(transaction, inverted, tree)?;
573+
574+
return Ok(transaction.repo().find_tree(tree::overlay(
575+
transaction.repo(),
576+
new_tree.id(),
577+
stripped,
578+
)?)?);
579+
}
580+
581+
if let Some(ws) = unapply_workspace(
582+
transaction,
583+
&to_op(filter),
584+
tree.clone(),
585+
parent_tree.clone(),
586+
)? {
587+
return Ok(ws);
588+
}
589+
590+
if let Op::Chain(a, b) = to_op(filter) {
591+
let p = apply(transaction, a, parent_tree.clone())?;
592+
return unapply(
593+
transaction,
594+
a,
595+
unapply(transaction, b, tree, p)?,
596+
parent_tree,
597+
);
598+
}
599+
600+
return Err(josh_error("filter cannot be unapplied"));
567601
}
568602

569-
fn unapply2<'a>(
603+
fn unapply_workspace<'a>(
570604
transaction: &'a cache::Transaction,
571605
op: &Op,
572606
tree: git2::Tree<'a>,
573607
parent_tree: git2::Tree<'a>,
574-
) -> JoshResult<git2::Tree<'a>> {
608+
) -> JoshResult<Option<git2::Tree<'a>>> {
575609
return match op {
576-
Op::Nop => Ok(tree),
577-
Op::Linear => Ok(tree),
578-
Op::Empty => Ok(parent_tree),
579-
580-
Op::Chain(a, b) => {
581-
let p = apply(transaction, *a, parent_tree.clone())?;
582-
let x = unapply(transaction, *b, tree, p)?;
583-
unapply(transaction, *a, x, parent_tree)
584-
}
585610
Op::Workspace(path) => {
586-
let root = to_filter(Op::Subdir(path.to_owned()));
587-
let mapped = &tree::get_blob(transaction.repo(), &tree, Path::new("workspace.josh"));
588-
let parsed = parse(mapped)?;
589-
590-
let mut blob = String::new();
591-
if let Ok(c) = get_comments(mapped) {
592-
if !c.is_empty() {
593-
blob = c;
594-
}
595-
}
596-
let blob = &format!("{}{}\n", &blob, pretty(parsed, 0));
611+
let tree = pre_process_tree(transaction.repo(), tree)?;
612+
let workspace = get_workspace(transaction.repo(), &tree, &Path::new(""));
613+
let original_workspace = get_workspace(transaction.repo(), &parent_tree, &path);
597614

598-
// Remove workspace.josh from the tree to prevent it from being parsed again
599-
// further down the callstack leading to endless recursion.
600-
let tree = tree::insert(
601-
transaction.repo(),
602-
&tree,
603-
Path::new("workspace.josh"),
604-
git2::Oid::zero(),
605-
0o0100644,
606-
)?;
607-
608-
// Insert a dummy file to prevent the directory from dissappearing through becoming
609-
// empty.
610-
let tree = tree::insert(
611-
transaction.repo(),
612-
&tree,
613-
Path::new("DUMMY-df97a89d-b11f-4e1c-8400-345f895f0d40"),
614-
transaction.repo().blob("".as_bytes())?,
615-
0o0100644,
616-
)?;
617-
618-
let r = unapply(
615+
let root = to_filter(Op::Subdir(path.to_owned()));
616+
let filter = compose(workspace, root);
617+
let original_filter = compose(original_workspace, root);
618+
let matching = apply(
619619
transaction,
620-
compose(root, parsed),
621-
tree.clone(),
622-
parent_tree,
620+
chain(original_filter, opt::invert(original_filter)?),
621+
parent_tree.clone(),
623622
)?;
623+
let stripped = tree::subtract(transaction, parent_tree.id(), matching.id())?;
624+
let new_tree = apply(transaction, opt::invert(filter)?, tree)?;
624625

625-
// Remove the dummy file inserted above
626-
let r = tree::insert(
626+
let result = transaction.repo().find_tree(tree::overlay(
627627
transaction.repo(),
628-
&r,
629-
&path.join("DUMMY-df97a89d-b11f-4e1c-8400-345f895f0d40"),
630-
git2::Oid::zero(),
631-
0o0100644,
632-
)?;
633-
634-
// Put the workspace.josh file back to it's target location.
635-
let r = if !mapped.is_empty() {
636-
tree::insert(
637-
transaction.repo(),
638-
&r,
639-
&path.join("workspace.josh"),
640-
transaction.repo().blob(blob.as_bytes())?,
641-
0o0100644, // Should this handle filemode?
642-
)?
643-
} else {
644-
r
645-
};
628+
new_tree.id(),
629+
stripped,
630+
)?)?;
646631

647-
return Ok(r);
632+
return Ok(Some(result));
648633
}
649-
Op::Compose(filters) => {
650-
let mut remaining = tree.clone();
651-
let mut result = parent_tree.clone();
652-
653-
for other in filters.iter().rev() {
654-
let from_empty = unapply(
655-
transaction,
656-
*other,
657-
remaining.clone(),
658-
tree::empty(transaction.repo()),
659-
)?;
660-
if tree::empty_id() == from_empty.id() {
661-
continue;
662-
}
663-
result = unapply(transaction, *other, remaining.clone(), result)?;
664-
let reapply = apply(transaction, *other, from_empty.clone())?;
665-
666-
remaining = transaction.repo().find_tree(tree::subtract(
667-
transaction,
668-
remaining.id(),
669-
reapply.id(),
670-
)?)?;
671-
}
634+
_ => Ok(None),
635+
};
636+
}
672637

673-
return Ok(result);
674-
}
638+
fn pre_process_tree<'a>(
639+
repo: &'a git2::Repository,
640+
tree: git2::Tree<'a>,
641+
) -> JoshResult<git2::Tree<'a>> {
642+
let path = std::path::Path::new("workspace.josh");
643+
let ws_file = filter::tree::get_blob(repo, &tree, &path);
644+
let parsed = filter::parse(&ws_file)?;
675645

676-
Op::File(path) => {
677-
let (file, mode) = tree
678-
.get_path(path)
679-
.map(|x| (x.id(), x.filemode()))
680-
.unwrap_or((git2::Oid::zero(), 0o0100644));
681-
if let Ok(_) = transaction.repo().find_blob(file) {
682-
tree::insert(transaction.repo(), &parent_tree, path, file, mode)
683-
} else {
684-
tree::insert(
685-
transaction.repo(),
686-
&parent_tree,
687-
path,
688-
git2::Oid::zero(),
689-
0o0100644,
690-
)
691-
}
692-
}
646+
if !invert(parsed).is_ok() {
647+
return Err(josh_error("Invalid workspace: not reversible"));
648+
}
693649

694-
Op::Subtract(_, _) => return Err(josh_error("filter not reversible")),
695-
Op::Exclude(b) => {
696-
let subtracted = tree::subtract(
697-
transaction,
698-
tree.id(),
699-
unapply(transaction, *b, tree, tree::empty(transaction.repo()))?.id(),
700-
)?;
701-
Ok(transaction.repo().find_tree(tree::overlay(
702-
transaction.repo(),
703-
parent_tree.id(),
704-
subtracted,
705-
)?)?)
650+
let mut blob = String::new();
651+
if let Ok(c) = filter::get_comments(&ws_file) {
652+
if !c.is_empty() {
653+
blob = c;
706654
}
707-
Op::Glob(pattern) => {
708-
let pattern = glob::Pattern::new(pattern)?;
709-
let options = glob::MatchOptions {
710-
case_sensitive: true,
711-
require_literal_separator: true,
712-
require_literal_leading_dot: true,
713-
};
714-
let subtracted = tree::remove_pred(
715-
transaction,
716-
"",
717-
tree.id(),
718-
&|path, isblob| isblob && (pattern.matches_path_with(path, options)),
719-
to_filter(op.clone()).id(),
720-
)?;
721-
Ok(transaction.repo().find_tree(tree::overlay(
722-
transaction.repo(),
723-
parent_tree.id(),
724-
subtracted.id(),
725-
)?)?)
726-
}
727-
Op::Prefix(path) => Ok(tree
728-
.get_path(path)
729-
.and_then(|x| transaction.repo().find_tree(x.id()))
730-
.unwrap_or(tree::empty(transaction.repo()))),
731-
Op::Subdir(path) => {
732-
tree::insert(transaction.repo(), &parent_tree, path, tree.id(), 0o0040000)
733-
}
734-
_ => return Err(josh_error("filter not reversible")),
735-
};
655+
}
656+
let blob = &format!("{}{}\n", &blob, filter::pretty(parsed, 0));
657+
658+
let tree = filter::tree::insert(
659+
repo,
660+
&tree,
661+
&path,
662+
repo.blob(blob.as_bytes())?,
663+
0o0100644, // Should this handle filemode?
664+
)?;
665+
666+
Ok(tree)
736667
}
737668

738669
/// Create a filter that is the result of feeding the output of `first` into `second`

0 commit comments

Comments
 (0)