ok/jj
1
0
Fork 0
forked from mirrors/jj

cli: use MergedTree for finding conflicts

`MergedTree` is now ready to be used when checking if a commit has
conflicts, and when listing conflicts. We don't yet a way for the user
to say they want to use tree-level conflicts even for these
cases. However, since the backend can decide, we should be able to
have our backend return tree-level conflicts. All writes will still
use path-level conflicts, so the experimentation we can do at Google
is limited.

Beacause `MergedTree` doesn't yet have a way of walking conflicts
while restricting it by a matcher, this will make `jj resolve` a
little slower. I suspect no one will notice.
This commit is contained in:
Martin von Zweigbergk 2023-05-30 15:17:04 -07:00 committed by Martin von Zweigbergk
parent 006c764694
commit 1c3fe9a651
6 changed files with 42 additions and 21 deletions

View file

@ -20,7 +20,8 @@ use std::hash::{Hash, Hasher};
use std::sync::Arc;
use crate::backend;
use crate::backend::{ChangeId, CommitId, Signature, TreeId};
use crate::backend::{BackendError, ChangeId, CommitId, Signature, TreeId};
use crate::merged_tree::MergedTree;
use crate::repo_path::RepoPath;
use crate::store::Store;
use crate::tree::Tree;
@ -101,12 +102,27 @@ impl Commit {
.collect()
}
// TODO(#1624): Delete when all callers use `merged_tree()`
pub fn tree(&self) -> Tree {
self.store
.get_tree(&RepoPath::root(), self.data.root_tree.as_legacy_tree_id())
.unwrap()
}
pub fn merged_tree(&self) -> Result<MergedTree, BackendError> {
if self.data.uses_tree_conflict_format {
let tree_conflict = self
.data
.root_tree
.try_map(|tree_id| self.store.get_tree(&RepoPath::root(), tree_id))?;
Ok(MergedTree::new(tree_conflict))
} else {
let tree_id = self.data.root_tree.as_legacy_tree_id();
let tree = self.store.get_tree(&RepoPath::root(), tree_id)?;
Ok(MergedTree::legacy(tree))
}
}
pub fn tree_id(&self) -> &TreeId {
self.data.root_tree.as_legacy_tree_id()
}

View file

@ -870,7 +870,7 @@ fn build_predicate_fn<'index>(
}
RevsetFilterPredicate::HasConflict => pure_predicate_fn(move |entry| {
let commit = store.get_commit(&entry.commit_id()).unwrap();
commit.tree().has_conflict()
commit.merged_tree().unwrap().has_conflict()
}),
}
}

View file

@ -194,6 +194,14 @@ impl MergedTree {
pub fn conflicts(&self) -> impl Iterator<Item = (RepoPath, Conflict<Option<TreeValue>>)> {
ConflictIterator::new(self.clone())
}
/// Whether this tree has conflicts.
pub fn has_conflict(&self) -> bool {
match self {
MergedTree::Legacy(tree) => tree.has_conflict(),
MergedTree::Merge(conflict) => !conflict.is_resolved(),
}
}
}
fn all_tree_conflict_names(conflict: &Conflict<Tree>) -> impl Iterator<Item = &RepoPathComponent> {

View file

@ -845,7 +845,7 @@ fn cmd_git_push(
{
reasons.push("it has no author and/or committer set");
}
if commit.tree().has_conflict() {
if commit.merged_tree()?.has_conflict() {
reasons.push("it has conflicts");
}
if !reasons.is_empty() {

View file

@ -1524,13 +1524,13 @@ fn cmd_status(
)?;
}
let conflicts = tree.conflicts();
let conflicts = wc_commit.merged_tree()?.conflicts().collect_vec();
if !conflicts.is_empty() {
writeln!(
formatter.labeled("conflict"),
"There are unresolved conflicts at these paths:"
)?;
print_conflicted_paths(&conflicts, &tree, formatter, &workspace_command)?
print_conflicted_paths(&conflicts, formatter, &workspace_command)?
}
}
@ -2667,8 +2667,11 @@ fn cmd_resolve(
let mut workspace_command = command.workspace_helper(ui)?;
let matcher = workspace_command.matcher_from_values(&args.paths)?;
let commit = workspace_command.resolve_single_rev(&args.revision)?;
let tree = commit.tree();
let conflicts = tree.conflicts_matching(matcher.as_ref());
let tree = commit.merged_tree()?;
let conflicts = tree
.conflicts()
.filter(|path| matcher.matches(&path.0))
.collect_vec();
if conflicts.is_empty() {
return Err(CommandError::CliError(format!(
"No conflicts found {}",
@ -2682,7 +2685,6 @@ fn cmd_resolve(
if args.list {
return print_conflicted_paths(
&conflicts,
&tree,
ui.stdout_formatter().as_mut(),
&workspace_command,
);
@ -2703,13 +2705,12 @@ fn cmd_resolve(
tx.finish(ui)?;
if !args.quiet {
let new_tree = new_commit.tree();
let new_conflicts = new_tree.conflicts_matching(&EverythingMatcher);
let new_tree = new_commit.merged_tree()?;
let new_conflicts = new_tree.conflicts().collect_vec();
if !new_conflicts.is_empty() {
ui.write("After this operation, some files at this revision still have conflicts:\n")?;
print_conflicted_paths(
&new_conflicts,
&tree,
ui.stdout_formatter().as_mut(),
&workspace_command,
)?;
@ -2719,24 +2720,20 @@ fn cmd_resolve(
}
fn print_conflicted_paths(
conflicts: &[(RepoPath, jj_lib::backend::ConflictId)],
tree: &Tree,
conflicts: &[(RepoPath, Conflict<Option<TreeValue>>)],
formatter: &mut dyn Formatter,
workspace_command: &WorkspaceCommandHelper,
) -> Result<(), CommandError> {
let formatted_paths = conflicts
.iter()
.map(|(path, _id)| workspace_command.format_file_path(path))
.map(|(path, _conflict)| workspace_command.format_file_path(path))
.collect_vec();
let max_path_len = formatted_paths.iter().map(|p| p.len()).max().unwrap_or(0);
let formatted_paths = formatted_paths
.into_iter()
.map(|p| format!("{:width$}", p, width = max_path_len.min(32) + 3));
for ((repo_path, conflict_id), formatted_path) in
std::iter::zip(conflicts.iter(), formatted_paths)
{
let conflict = tree.store().read_conflict(repo_path, conflict_id)?;
for ((_, conflict), formatted_path) in std::iter::zip(conflicts.iter(), formatted_paths) {
let sides = conflict.adds().len();
let n_adds = conflict.adds().iter().flatten().count();
let deletions = sides - n_adds;

View file

@ -299,9 +299,9 @@ fn build_commit_keyword_opt<'repo>(
let maybe_entries = repo.resolve_change_id(commit.change_id());
maybe_entries.map_or(true, |entries| !entries.contains(commit.id()))
})),
"conflict" => {
language.wrap_boolean(wrap_fn(property, |commit| commit.tree().has_conflict()))
}
"conflict" => language.wrap_boolean(wrap_fn(property, |commit| {
commit.merged_tree().unwrap().has_conflict()
})),
"empty" => language.wrap_boolean(wrap_fn(property, |commit| {
let parent_tree = rewrite::merge_commit_trees(repo, &commit.parents()).unwrap();
commit.tree_id() == parent_tree.id()