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-05-16 05:16:07 +00:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Write;
|
2021-03-14 17:37:28 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::process::Command;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2022-04-24 05:47:36 +00:00
|
|
|
use itertools::Itertools;
|
2021-11-06 22:36:54 +00:00
|
|
|
use jujutsu_lib::backend::{BackendError, TreeId};
|
2022-03-10 06:41:09 +00:00
|
|
|
use jujutsu_lib::gitignore::GitIgnoreFile;
|
2021-06-05 21:29:40 +00:00
|
|
|
use jujutsu_lib::matchers::EverythingMatcher;
|
2021-05-19 16:41:25 +00:00
|
|
|
use jujutsu_lib::repo_path::RepoPath;
|
2022-03-11 23:33:03 +00:00
|
|
|
use jujutsu_lib::settings::UserSettings;
|
2021-09-12 06:52:38 +00:00
|
|
|
use jujutsu_lib::store::Store;
|
2022-04-24 05:47:36 +00:00
|
|
|
use jujutsu_lib::tree::Tree;
|
2021-05-15 16:16:31 +00:00
|
|
|
use jujutsu_lib::working_copy::{CheckoutError, TreeState};
|
2020-12-26 03:13:01 +00:00
|
|
|
use tempfile::tempdir;
|
|
|
|
use thiserror::Error;
|
|
|
|
|
2022-04-30 19:43:02 +00:00
|
|
|
#[derive(Debug, Error)]
|
2020-12-26 03:13:01 +00:00
|
|
|
pub enum DiffEditError {
|
|
|
|
#[error("The diff tool exited with a non-zero code")]
|
|
|
|
DifftoolAborted,
|
|
|
|
#[error("Failed to write directories to diff: {0:?}")]
|
|
|
|
CheckoutError(CheckoutError),
|
2022-04-30 19:43:02 +00:00
|
|
|
#[error("Error setting up temporary directory: {0:?}")]
|
|
|
|
SetUpDirError(#[source] std::io::Error),
|
|
|
|
#[error("Error executing editor '{editor_binary}': {source}")]
|
|
|
|
ExecuteEditorError {
|
|
|
|
editor_binary: String,
|
|
|
|
#[source]
|
|
|
|
source: std::io::Error,
|
|
|
|
},
|
|
|
|
#[error("I/O error: {0:?}")]
|
|
|
|
IoError(#[source] std::io::Error),
|
2020-12-26 03:13:01 +00:00
|
|
|
#[error("Internal error: {0:?}")]
|
2021-09-12 06:52:38 +00:00
|
|
|
InternalBackendError(BackendError),
|
2020-12-26 03:13:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<CheckoutError> for DiffEditError {
|
|
|
|
fn from(err: CheckoutError) -> Self {
|
|
|
|
DiffEditError::CheckoutError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
impl From<BackendError> for DiffEditError {
|
|
|
|
fn from(err: BackendError) -> Self {
|
|
|
|
DiffEditError::InternalBackendError(err)
|
2020-12-26 03:13:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_out(
|
2021-09-12 06:52:38 +00:00
|
|
|
store: Arc<Store>,
|
2020-12-26 03:13:01 +00:00
|
|
|
wc_dir: PathBuf,
|
|
|
|
state_dir: PathBuf,
|
2022-02-13 19:54:48 +00:00
|
|
|
tree: &Tree,
|
2022-04-24 05:47:36 +00:00
|
|
|
sparse_patterns: Vec<RepoPath>,
|
2020-12-26 03:13:01 +00:00
|
|
|
) -> Result<TreeState, DiffEditError> {
|
2022-04-30 19:43:02 +00:00
|
|
|
std::fs::create_dir(&wc_dir).map_err(DiffEditError::SetUpDirError)?;
|
|
|
|
std::fs::create_dir(&state_dir).map_err(DiffEditError::SetUpDirError)?;
|
2020-12-26 03:13:01 +00:00
|
|
|
let mut tree_state = TreeState::init(store, wc_dir, state_dir);
|
2022-04-24 05:47:36 +00:00
|
|
|
tree_state.set_sparse_patterns(sparse_patterns)?;
|
2022-02-13 19:54:48 +00:00
|
|
|
tree_state.check_out(tree)?;
|
2020-12-26 03:13:01 +00:00
|
|
|
Ok(tree_state)
|
|
|
|
}
|
|
|
|
|
2022-04-30 19:43:02 +00:00
|
|
|
fn set_readonly_recursively(path: &Path) -> Result<(), std::io::Error> {
|
2020-12-26 18:34:58 +00:00
|
|
|
if path.is_dir() {
|
2022-04-30 19:43:02 +00:00
|
|
|
for entry in path.read_dir()? {
|
|
|
|
set_readonly_recursively(&entry?.path())?;
|
2020-12-26 18:34:58 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-30 19:43:02 +00:00
|
|
|
let mut perms = std::fs::metadata(path)?.permissions();
|
2020-12-26 18:34:58 +00:00
|
|
|
perms.set_readonly(true);
|
2022-04-30 19:43:02 +00:00
|
|
|
std::fs::set_permissions(path, perms)
|
2020-12-26 18:34:58 +00:00
|
|
|
}
|
|
|
|
|
2021-05-16 05:16:07 +00:00
|
|
|
pub fn edit_diff(
|
2022-03-11 23:33:03 +00:00
|
|
|
settings: &UserSettings,
|
2021-05-16 05:16:07 +00:00
|
|
|
left_tree: &Tree,
|
|
|
|
right_tree: &Tree,
|
|
|
|
instructions: &str,
|
2022-03-10 06:41:09 +00:00
|
|
|
base_ignores: Arc<GitIgnoreFile>,
|
2021-05-16 05:16:07 +00:00
|
|
|
) -> Result<TreeId, DiffEditError> {
|
2020-12-26 03:13:01 +00:00
|
|
|
let store = left_tree.store();
|
2022-04-24 05:47:36 +00:00
|
|
|
let changed_files = left_tree
|
|
|
|
.diff(right_tree, &EverythingMatcher)
|
|
|
|
.map(|(path, _value)| path)
|
|
|
|
.collect_vec();
|
2020-12-26 03:13:01 +00:00
|
|
|
|
2022-04-24 05:47:36 +00:00
|
|
|
// Check out the two trees in temporary directories. Only include changed files
|
|
|
|
// in the sparse checkout patterns.
|
2022-04-30 19:43:02 +00:00
|
|
|
let temp_dir = tempdir().map_err(DiffEditError::SetUpDirError)?;
|
2020-12-26 03:13:01 +00:00
|
|
|
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,
|
2022-04-24 05:47:36 +00:00
|
|
|
left_tree,
|
|
|
|
changed_files.clone(),
|
2020-12-26 03:13:01 +00:00
|
|
|
)?;
|
2022-04-30 19:43:02 +00:00
|
|
|
set_readonly_recursively(&left_wc_dir).map_err(DiffEditError::SetUpDirError)?;
|
2020-12-26 03:13:01 +00:00
|
|
|
let mut right_tree_state = check_out(
|
|
|
|
store.clone(),
|
|
|
|
right_wc_dir.clone(),
|
|
|
|
right_state_dir,
|
2022-04-24 05:47:36 +00:00
|
|
|
right_tree,
|
|
|
|
changed_files,
|
2020-12-26 03:13:01 +00:00
|
|
|
)?;
|
2021-05-16 05:16:07 +00:00
|
|
|
let instructions_path = right_wc_dir.join("JJ-INSTRUCTIONS");
|
|
|
|
// In the unlikely event that the file already exists, then the user will simply
|
|
|
|
// not get any instructions.
|
|
|
|
let add_instructions = !instructions.is_empty() && !instructions_path.exists();
|
|
|
|
if add_instructions {
|
2022-04-30 19:43:02 +00:00
|
|
|
let mut file = File::create(&instructions_path).map_err(DiffEditError::SetUpDirError)?;
|
|
|
|
file.write_all(instructions.as_bytes())
|
|
|
|
.map_err(DiffEditError::SetUpDirError)?;
|
2021-05-16 05:16:07 +00:00
|
|
|
}
|
2020-12-26 03:13:01 +00:00
|
|
|
|
2021-05-31 16:05:16 +00:00
|
|
|
// TODO: Make this configuration have a table of possible editors and detect the
|
|
|
|
// best one here.
|
2022-03-11 23:33:03 +00:00
|
|
|
let editor_binary = settings
|
2021-05-31 16:05:16 +00:00
|
|
|
.config()
|
2022-03-10 02:04:19 +00:00
|
|
|
.get_string("ui.diff-editor")
|
2021-05-31 16:05:16 +00:00
|
|
|
.unwrap_or_else(|_| "meld".to_string());
|
2020-12-26 03:13:01 +00:00
|
|
|
// Start a diff editor on the two directories.
|
2021-05-31 16:05:16 +00:00
|
|
|
let exit_status = Command::new(&editor_binary)
|
2020-12-26 03:13:01 +00:00
|
|
|
.arg(&left_wc_dir)
|
|
|
|
.arg(&right_wc_dir)
|
|
|
|
.status()
|
2022-04-30 19:43:02 +00:00
|
|
|
.map_err(|e| DiffEditError::ExecuteEditorError {
|
|
|
|
editor_binary,
|
|
|
|
source: e,
|
|
|
|
})?;
|
2020-12-26 03:13:01 +00:00
|
|
|
if !exit_status.success() {
|
|
|
|
return Err(DiffEditError::DifftoolAborted);
|
|
|
|
}
|
2021-05-16 05:16:07 +00:00
|
|
|
if add_instructions {
|
|
|
|
std::fs::remove_file(instructions_path).ok();
|
|
|
|
}
|
2020-12-26 03:13:01 +00:00
|
|
|
|
2022-04-24 05:47:36 +00:00
|
|
|
Ok(right_tree_state.write_tree(base_ignores))
|
2020-12-26 03:13:01 +00:00
|
|
|
}
|