2020-12-26 19:47:13 +00:00
|
|
|
// Copyright 2020 Google LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2021-03-14 17:37:28 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::process::Command;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2021-05-15 16:16:31 +00:00
|
|
|
use jujutsu_lib::repo_path::{DirRepoPath, RepoPath};
|
|
|
|
use jujutsu_lib::store::{StoreError, TreeId, TreeValue};
|
|
|
|
use jujutsu_lib::store_wrapper::StoreWrapper;
|
|
|
|
use jujutsu_lib::tree::Tree;
|
|
|
|
use jujutsu_lib::tree_builder::TreeBuilder;
|
|
|
|
use jujutsu_lib::trees::merge_trees;
|
|
|
|
use jujutsu_lib::working_copy::{CheckoutError, TreeState};
|
2020-12-26 03:13:01 +00:00
|
|
|
use tempfile::tempdir;
|
|
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
#[derive(Debug, Error, PartialEq, Eq)]
|
|
|
|
pub enum DiffEditError {
|
|
|
|
#[error("The diff tool exited with a non-zero code")]
|
|
|
|
DifftoolAborted,
|
|
|
|
#[error("Failed to write directories to diff: {0:?}")]
|
|
|
|
CheckoutError(CheckoutError),
|
|
|
|
#[error("Internal error: {0:?}")]
|
|
|
|
InternalStoreError(StoreError),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<CheckoutError> for DiffEditError {
|
|
|
|
fn from(err: CheckoutError) -> Self {
|
|
|
|
DiffEditError::CheckoutError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<StoreError> for DiffEditError {
|
|
|
|
fn from(err: StoreError) -> Self {
|
|
|
|
DiffEditError::InternalStoreError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_to_tree(
|
|
|
|
store: &StoreWrapper,
|
|
|
|
tree_builder: &mut TreeBuilder,
|
|
|
|
repo_path: &RepoPath,
|
|
|
|
value: &TreeValue,
|
|
|
|
) -> Result<(), StoreError> {
|
|
|
|
match value {
|
|
|
|
TreeValue::Conflict(conflict_id) => {
|
|
|
|
let conflict = store.read_conflict(conflict_id)?;
|
|
|
|
let materialized_value =
|
2021-05-15 16:16:31 +00:00
|
|
|
jujutsu_lib::conflicts::conflict_to_materialized_value(store, repo_path, &conflict);
|
2020-12-26 03:13:01 +00:00
|
|
|
tree_builder.set(repo_path.clone(), materialized_value);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
tree_builder.set(repo_path.clone(), (*value).clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_out(
|
|
|
|
store: Arc<StoreWrapper>,
|
|
|
|
wc_dir: PathBuf,
|
|
|
|
state_dir: PathBuf,
|
|
|
|
tree_id: TreeId,
|
|
|
|
) -> Result<TreeState, DiffEditError> {
|
|
|
|
std::fs::create_dir(&wc_dir).unwrap();
|
|
|
|
std::fs::create_dir(&state_dir).unwrap();
|
|
|
|
let mut tree_state = TreeState::init(store, wc_dir, state_dir);
|
|
|
|
tree_state.check_out(tree_id)?;
|
|
|
|
Ok(tree_state)
|
|
|
|
}
|
|
|
|
|
2020-12-26 18:34:58 +00:00
|
|
|
fn set_readonly_recursively(path: &Path) {
|
|
|
|
if path.is_dir() {
|
|
|
|
for entry in path.read_dir().unwrap() {
|
|
|
|
set_readonly_recursively(&entry.unwrap().path());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let mut perms = std::fs::metadata(path).unwrap().permissions();
|
|
|
|
perms.set_readonly(true);
|
|
|
|
std::fs::set_permissions(path, perms).unwrap();
|
|
|
|
}
|
|
|
|
|
2020-12-26 03:13:01 +00:00
|
|
|
pub fn edit_diff(left_tree: &Tree, right_tree: &Tree) -> Result<TreeId, DiffEditError> {
|
|
|
|
// First create partial Trees of only the subset of the left and right trees
|
|
|
|
// that affect files changed between them.
|
|
|
|
let store = left_tree.store();
|
|
|
|
let mut left_tree_builder = store.tree_builder(store.empty_tree_id().clone());
|
|
|
|
let mut right_tree_builder = store.tree_builder(store.empty_tree_id().clone());
|
2021-04-07 18:27:40 +00:00
|
|
|
for (file_path, diff) in left_tree.diff(&right_tree) {
|
2020-12-26 03:13:01 +00:00
|
|
|
let (left_value, right_value) = diff.as_options();
|
|
|
|
let repo_path = file_path.to_repo_path();
|
|
|
|
if let Some(value) = left_value {
|
|
|
|
add_to_tree(store, &mut left_tree_builder, &repo_path, value).unwrap();
|
|
|
|
}
|
|
|
|
if let Some(value) = right_value {
|
|
|
|
add_to_tree(store, &mut right_tree_builder, &repo_path, value).unwrap();
|
|
|
|
}
|
2021-04-07 18:27:40 +00:00
|
|
|
}
|
2020-12-26 03:13:01 +00:00
|
|
|
let left_partial_tree_id = left_tree_builder.write_tree();
|
|
|
|
let right_partial_tree_id = right_tree_builder.write_tree();
|
|
|
|
let right_partial_tree = store.get_tree(&DirRepoPath::root(), &right_partial_tree_id)?;
|
|
|
|
|
|
|
|
// Check out the two partial trees in temporary directories.
|
|
|
|
let temp_dir = tempdir().unwrap();
|
|
|
|
let left_wc_dir = temp_dir.path().join("left");
|
|
|
|
let left_state_dir = temp_dir.path().join("left_state");
|
|
|
|
let right_wc_dir = temp_dir.path().join("right");
|
|
|
|
let right_state_dir = temp_dir.path().join("right_state");
|
|
|
|
check_out(
|
|
|
|
store.clone(),
|
|
|
|
left_wc_dir.clone(),
|
|
|
|
left_state_dir,
|
|
|
|
left_partial_tree_id,
|
|
|
|
)?;
|
2020-12-26 18:34:58 +00:00
|
|
|
set_readonly_recursively(&left_wc_dir);
|
2020-12-26 03:13:01 +00:00
|
|
|
let mut right_tree_state = check_out(
|
|
|
|
store.clone(),
|
|
|
|
right_wc_dir.clone(),
|
|
|
|
right_state_dir,
|
|
|
|
right_partial_tree_id,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Start a diff editor on the two directories.
|
|
|
|
let exit_status = Command::new("meld")
|
|
|
|
.arg(&left_wc_dir)
|
|
|
|
.arg(&right_wc_dir)
|
|
|
|
.status()
|
|
|
|
.expect("failed to run diff editor");
|
|
|
|
if !exit_status.success() {
|
|
|
|
return Err(DiffEditError::DifftoolAborted);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a Tree based on the initial right tree, applying the changes made to
|
|
|
|
// that directory by the diff editor.
|
|
|
|
let new_right_partial_tree_id = right_tree_state.write_tree();
|
|
|
|
let new_right_partial_tree =
|
|
|
|
store.get_tree(&DirRepoPath::root(), &new_right_partial_tree_id)?;
|
|
|
|
let new_tree_id = merge_trees(right_tree, &right_partial_tree, &new_right_partial_tree)?;
|
|
|
|
|
|
|
|
Ok(new_tree_id)
|
|
|
|
}
|