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:
parent
006c764694
commit
1c3fe9a651
6 changed files with 42 additions and 21 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue