Add branch creation and file history search
Implement the ability to create new local branches directly from the application's UI. This includes a new backend command to handle branch creation with validation and a frontend form in the branch panel. Additionally, extend the global search dialog to include a "Files" tab. Users can now search for files by name or path within the repository. Selecting a file in the search results displays its full commit history, with options to diff the file at a specific commit or restore it to that version.
This commit is contained in:
@@ -281,6 +281,14 @@ pub fn checkout_branch(path: String, branch: String) -> Result<GitStatus, String
|
||||
status_for_repo(&repo)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_branch(path: String, branch: String) -> Result<GitStatus, String> {
|
||||
let repo = resolve_repo(&path)?;
|
||||
let branch = validate_new_branch_name(&repo, &branch)?;
|
||||
run_git(&repo, ["checkout", "-b", branch.as_str()])?;
|
||||
status_for_repo(&repo)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn stage_files(path: String, files: Vec<String>) -> Result<GitStatus, String> {
|
||||
let repo = resolve_repo(&path)?;
|
||||
@@ -1839,6 +1847,38 @@ fn local_branch_name_for_remote(remote_branch: &str) -> Option<&str> {
|
||||
.filter(|local| !local.is_empty())
|
||||
}
|
||||
|
||||
fn validate_new_branch_name(repo: &Path, branch: &str) -> Result<String, String> {
|
||||
let branch = branch.trim();
|
||||
if branch.is_empty() {
|
||||
return Err("Branch-Name darf nicht leer sein.".to_string());
|
||||
}
|
||||
|
||||
let output = git_command()
|
||||
.arg("-C")
|
||||
.arg(repo)
|
||||
.args(["check-ref-format", "--branch", branch])
|
||||
.output()
|
||||
.map_err(|err| format!("Git konnte nicht gestartet werden. Ist Git installiert? {err}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let details = command_output_details(&output);
|
||||
return Err(format!("Ungueltiger Branch-Name: {details}"));
|
||||
}
|
||||
|
||||
let normalized = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let normalized = if normalized.is_empty() {
|
||||
branch.to_string()
|
||||
} else {
|
||||
normalized
|
||||
};
|
||||
|
||||
if ref_exists(repo, &format!("refs/heads/{normalized}"))? {
|
||||
return Err(format!("Branch '{normalized}' existiert bereits."));
|
||||
}
|
||||
|
||||
Ok(normalized)
|
||||
}
|
||||
|
||||
fn ref_exists(repo: &Path, ref_name: &str) -> Result<bool, String> {
|
||||
let output = git_command()
|
||||
.arg("-C")
|
||||
@@ -3186,6 +3226,31 @@ mod tests {
|
||||
assert_eq!(plan, CheckoutPlan::Local("feature/demo".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_branch_creates_and_checks_out_local_branch() {
|
||||
let repo = init_temp_repo("create_branch");
|
||||
commit_initial_file(&repo.path);
|
||||
|
||||
let status = create_branch(
|
||||
repo.path.to_string_lossy().to_string(),
|
||||
"feature/new-panel".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(status.current_branch.as_deref(), Some("feature/new-panel"));
|
||||
assert!(
|
||||
ref_exists(&repo.path, "refs/heads/feature/new-panel").unwrap(),
|
||||
"new branch should exist"
|
||||
);
|
||||
|
||||
let err = create_branch(
|
||||
repo.path.to_string_lossy().to_string(),
|
||||
"feature/new-panel".to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.contains("existiert bereits"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restore_to_commit_resets_branch_to_selected_commit() {
|
||||
let repo = init_temp_repo("restore_to_commit");
|
||||
|
||||
@@ -4,11 +4,11 @@ mod git;
|
||||
|
||||
use git::{
|
||||
cancel_code_search, checkout_branch, commit, compare_commits, compare_file_to_head,
|
||||
compare_file_to_parent, cred_delete, cred_load, cred_save, diff_file_against_working_tree,
|
||||
get_remote_url, get_status, list_branches, list_commits, list_file_history,
|
||||
list_repository_files, merge_branch, open_repository, pull, push, read_conflict,
|
||||
resolve_conflict, resolve_conflict_side, restore_file_from_commit, restore_files,
|
||||
restore_to_commit, search_code_introductions, stage_files, unstage_files,
|
||||
compare_file_to_parent, create_branch, cred_delete, cred_load, cred_save,
|
||||
diff_file_against_working_tree, get_remote_url, get_status, list_branches, list_commits,
|
||||
list_file_history, list_repository_files, merge_branch, open_repository, pull, push,
|
||||
read_conflict, resolve_conflict, resolve_conflict_side, restore_file_from_commit,
|
||||
restore_files, restore_to_commit, search_code_introductions, stage_files, unstage_files,
|
||||
SearchCancellationState,
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ fn main() {
|
||||
get_status,
|
||||
list_branches,
|
||||
checkout_branch,
|
||||
create_branch,
|
||||
stage_files,
|
||||
unstage_files,
|
||||
restore_files,
|
||||
|
||||
Reference in New Issue
Block a user