Modify checkout implementation making branch switching more similar to CLI git behaviour (#1809)

This commit is contained in:
lightsnowball 2023-08-18 14:31:51 +02:00 committed by GitHub
parent 074820e63b
commit 514e8f0175
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 23 deletions

View file

@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* fix expansion of `~` in `commit.template` ([#1745](https://github.com/extrawurst/gitui/pull/1745))
* fix hunk (un)staging/reset for # of context lines != 3 ([#1746](https://github.com/extrawurst/gitui/issues/1746))
* fix delay when opening external editor ([#1506](https://github.com/extrawurst/gitui/issues/1506))
* checkout branch works with non-empty status report ([#1399](https://github.com/extrawurst/gitui/issues/1399))
## [0.23.0] - 2022-06-19

View file

@ -285,37 +285,36 @@ pub fn branch_compare_upstream(
Ok(BranchCompare { ahead, behind })
}
/// Modify HEAD to point to a branch then checkout head, does not work if there are uncommitted changes
/// Switch branch to given `branch_ref`.
///
/// Method will fail if there are conflicting changes between current and target branch. However,
/// if files are not conflicting, they will remain in tree (e.g. tracked new file is not
/// conflicting and therefore is kept in tree even after checkout).
pub fn checkout_branch(
repo_path: &RepoPath,
branch_ref: &str,
) -> Result<()> {
scope_time!("checkout_branch");
// This defaults to a safe checkout, so don't delete anything that
// hasn't been committed or stashed, in this case it will Err
let repo = repo(repo_path)?;
let cur_ref = repo.head()?;
let statuses = repo.statuses(Some(
git2::StatusOptions::new().include_ignored(false),
))?;
if statuses.is_empty() {
repo.set_head(branch_ref)?;
let branch_name =
branch_ref.split('/').last().ok_or(Error::PathString)?;
if let Err(e) = repo.checkout_head(Some(
git2::build::CheckoutBuilder::new().force(),
)) {
// This is safe beacuse cur_ref was just found
repo.set_head(
bytes2string(cur_ref.name_bytes())?.as_str(),
)?;
return Err(Error::Git(e));
}
Ok(())
} else {
Err(Error::UncommittedChanges)
}
let branch = repo.find_branch(branch_name, BranchType::Local)?;
let target_treeish = branch.into_reference().peel_to_tree()?;
let target_treeish_object = target_treeish.as_object();
// modify state to match branch's state
repo.checkout_tree(
target_treeish_object,
Some(&mut git2::build::CheckoutBuilder::new()),
)?;
// modify HEAD to point to given branch
repo.set_head(branch_ref)?;
Ok(())
}
/// Detach HEAD to point to a commit then checkout HEAD, does not work if there are uncommitted changes
@ -678,7 +677,8 @@ mod tests_branches {
#[cfg(test)]
mod tests_checkout {
use super::*;
use crate::sync::tests::repo_init;
use crate::sync::{stage_add_file, tests::repo_init};
use std::{fs::File, path::Path};
#[test]
fn test_smoke() {
@ -710,6 +710,24 @@ mod tests_checkout {
);
assert!(checkout_branch(repo_path, "refs/heads/test").is_ok());
}
#[test]
fn test_staged_new_file() {
let (_td, repo) = repo_init().unwrap();
let root = repo.path().parent().unwrap();
let repo_path: &RepoPath =
&root.as_os_str().to_str().unwrap().into();
create_branch(repo_path, "test").unwrap();
let filename = "file.txt";
let file = root.join(filename);
File::create(&file).unwrap();
stage_add_file(&repo_path, &Path::new(filename)).unwrap();
assert!(checkout_branch(repo_path, "refs/heads/test").is_ok());
}
}
#[cfg(test)]