almost
This commit is contained in:
+143
-2
@@ -82,6 +82,15 @@ pub struct GitCommitComparison {
|
||||
pub patch: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct ConflictFile {
|
||||
pub path: String,
|
||||
pub content: String,
|
||||
pub ours: Option<String>,
|
||||
pub theirs: Option<String>,
|
||||
pub base: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct GitRepositoryFile {
|
||||
pub path: String,
|
||||
@@ -260,8 +269,39 @@ pub fn merge_branch(path: String, branch: String) -> Result<GitStatus, String> {
|
||||
return Err("Branch-Name darf nicht leer sein.".to_string());
|
||||
}
|
||||
|
||||
run_git(&repo, ["merge", "--no-edit", branch])?;
|
||||
status_for_repo(&repo)
|
||||
let output = Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(&repo)
|
||||
.args(["merge", "--no-edit", branch])
|
||||
.output()
|
||||
.map_err(|err| format!("Git konnte nicht gestartet werden. Ist Git installiert? {err}"))?;
|
||||
|
||||
if output.status.success() {
|
||||
return status_for_repo(&repo);
|
||||
}
|
||||
|
||||
// A merge that stops on conflicts leaves unmerged paths in the work tree.
|
||||
// Surface those through the status so the UI can offer conflict resolution
|
||||
// instead of treating the conflict as a hard error.
|
||||
let status = status_for_repo(&repo)?;
|
||||
if status.files.iter().any(|file| {
|
||||
matches!(file.staged, Some(FileStatusKind::Conflicted))
|
||||
|| matches!(file.unstaged, Some(FileStatusKind::Conflicted))
|
||||
}) {
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let details = if !stderr.trim().is_empty() {
|
||||
stderr.trim()
|
||||
} else if !stdout.trim().is_empty() {
|
||||
stdout.trim()
|
||||
} else {
|
||||
"unbekannter Fehler"
|
||||
};
|
||||
|
||||
Err(format!("Merge fehlgeschlagen: {details}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -435,6 +475,56 @@ pub fn diff_file_against_working_tree(
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn read_conflict(path: String, file: String) -> Result<ConflictFile, String> {
|
||||
let repo = resolve_repo(&path)?;
|
||||
validate_files(std::slice::from_ref(&file))?;
|
||||
|
||||
let content = std::fs::read_to_string(repo.join(&file))
|
||||
.map_err(|err| format!("Konfliktdatei konnte nicht gelesen werden: {err}"))?;
|
||||
|
||||
Ok(ConflictFile {
|
||||
base: read_index_stage(&repo, 1, &file),
|
||||
ours: read_index_stage(&repo, 2, &file),
|
||||
theirs: read_index_stage(&repo, 3, &file),
|
||||
path: file,
|
||||
content,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn resolve_conflict(path: String, file: String, content: String) -> Result<GitStatus, String> {
|
||||
let repo = resolve_repo(&path)?;
|
||||
validate_files(std::slice::from_ref(&file))?;
|
||||
|
||||
let target = repo.join(&file);
|
||||
if let Some(parent) = target.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.map_err(|err| format!("Verzeichnis konnte nicht erstellt werden: {err}"))?;
|
||||
}
|
||||
std::fs::write(&target, content)
|
||||
.map_err(|err| format!("Konfliktdatei konnte nicht geschrieben werden: {err}"))?;
|
||||
|
||||
run_git_with_paths(&repo, &["add"], std::slice::from_ref(&file))?;
|
||||
status_for_repo(&repo)
|
||||
}
|
||||
|
||||
fn read_index_stage(repo: &Path, stage: u8, file: &str) -> Option<String> {
|
||||
let spec = format!(":{stage}:{file}");
|
||||
let output = Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(repo)
|
||||
.args(["show", spec.as_str()])
|
||||
.output()
|
||||
.ok()?;
|
||||
|
||||
if output.status.success() {
|
||||
Some(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn short_hash(hash: &str) -> String {
|
||||
hash.chars().take(7).collect()
|
||||
}
|
||||
@@ -1505,6 +1595,57 @@ mod tests {
|
||||
assert!(comparison.patch.contains("working tree change"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_and_resolve_conflict_round_trip() {
|
||||
let repo = init_temp_repo("resolve_conflict");
|
||||
fs::write(repo.path.join("file.txt"), "base\n").expect("base file should be written");
|
||||
run_git_test(&repo.path, ["add", "file.txt"]);
|
||||
run_git_test(&repo.path, ["commit", "-q", "-m", "base"]);
|
||||
let main_branch = git_output_test(&repo.path, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
||||
|
||||
run_git_test(&repo.path, ["checkout", "-q", "-b", "feature"]);
|
||||
fs::write(repo.path.join("file.txt"), "theirs change\n").expect("feature change");
|
||||
run_git_test(&repo.path, ["commit", "-q", "-am", "feature change"]);
|
||||
|
||||
run_git_test(&repo.path, ["checkout", "-q", main_branch.as_str()]);
|
||||
fs::write(repo.path.join("file.txt"), "ours change\n").expect("main change");
|
||||
run_git_test(&repo.path, ["commit", "-q", "-am", "main change"]);
|
||||
|
||||
// The merge is expected to fail with a conflict, so run git directly.
|
||||
let _ = Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(&repo.path)
|
||||
.args(["merge", "--no-edit", "feature"])
|
||||
.output()
|
||||
.expect("git merge should start");
|
||||
|
||||
let conflict =
|
||||
read_conflict(repo.path.to_string_lossy().to_string(), "file.txt".to_string()).unwrap();
|
||||
assert_eq!(
|
||||
conflict.ours.unwrap().replace("\r\n", "\n"),
|
||||
"ours change\n"
|
||||
);
|
||||
assert_eq!(
|
||||
conflict.theirs.unwrap().replace("\r\n", "\n"),
|
||||
"theirs change\n"
|
||||
);
|
||||
assert!(conflict.content.contains("<<<<<<<"));
|
||||
|
||||
let status = resolve_conflict(
|
||||
repo.path.to_string_lossy().to_string(),
|
||||
"file.txt".to_string(),
|
||||
"resolved\n".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(!status.files.iter().any(|file| {
|
||||
matches!(file.staged, Some(FileStatusKind::Conflicted))
|
||||
|| matches!(file.unstaged, Some(FileStatusKind::Conflicted))
|
||||
}));
|
||||
let contents = fs::read_to_string(repo.path.join("file.txt")).unwrap();
|
||||
assert_eq!(contents.replace("\r\n", "\n"), "resolved\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restore_staged_added_file_removes_it_from_index_and_worktree() {
|
||||
let repo = init_temp_repo("restore_staged_added_file");
|
||||
|
||||
@@ -5,8 +5,8 @@ mod git;
|
||||
use git::{
|
||||
checkout_branch, commit, compare_commits, diff_file_against_working_tree, get_status,
|
||||
list_branches, list_commits, list_file_history, list_repository_files, merge_branch,
|
||||
open_repository, pull, push, restore_file_from_commit, restore_files, restore_to_commit,
|
||||
stage_files, unstage_files,
|
||||
open_repository, pull, push, read_conflict, resolve_conflict, restore_file_from_commit,
|
||||
restore_files, restore_to_commit, stage_files, unstage_files,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
@@ -29,7 +29,9 @@ fn main() {
|
||||
list_repository_files,
|
||||
list_file_history,
|
||||
compare_commits,
|
||||
diff_file_against_working_tree
|
||||
diff_file_against_working_tree,
|
||||
read_conflict,
|
||||
resolve_conflict
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
Reference in New Issue
Block a user