mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
fix(branch): block checkout of remote HEAD symbolic refs (#2681)
Hide remote `*/HEAD` entries from the branch list and reject checkout attempts so gitui no longer creates a local `HEAD` branch. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
8619c07f3f
commit
dfc00d2e7f
2 changed files with 59 additions and 9 deletions
|
|
@ -372,6 +372,23 @@ pub fn checkout_commit(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether `name` refers to a remote symbolic `HEAD` ref (e.g. `origin/HEAD`).
|
||||||
|
pub fn is_remote_head_ref(name: &str) -> bool {
|
||||||
|
name == "HEAD"
|
||||||
|
|| name
|
||||||
|
.split_once('/')
|
||||||
|
.is_some_and(|(_, local)| local == "HEAD")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_branch_name_from_remote(remote_name: &str) -> Option<String> {
|
||||||
|
if is_remote_head_ref(remote_name) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let local = remote_name.split_once('/')?.1;
|
||||||
|
Some(local.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn checkout_remote_branch(
|
pub fn checkout_remote_branch(
|
||||||
repo_path: &RepoPath,
|
repo_path: &RepoPath,
|
||||||
|
|
@ -391,10 +408,11 @@ pub fn checkout_remote_branch(
|
||||||
return Err(Error::UncommittedChanges);
|
return Err(Error::UncommittedChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = branch.name.find('/').map_or_else(
|
let name = local_branch_name_from_remote(&branch.name).ok_or_else(|| {
|
||||||
|| branch.name.clone(),
|
Error::Generic(
|
||||||
|pos| branch.name[pos..].to_string(),
|
"cannot checkout remote HEAD reference".to_string(),
|
||||||
);
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let commit = repo.find_commit(branch.top_commit.into())?;
|
let commit = repo.find_commit(branch.top_commit.into())?;
|
||||||
let mut new_branch = repo.branch(&name, &commit, false)?;
|
let mut new_branch = repo.branch(&name, &commit, false)?;
|
||||||
|
|
@ -1021,6 +1039,40 @@ mod test_remote_branches {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_checkout_remote_head_fails() {
|
||||||
|
let (r1_dir, _repo) = repo_init_bare().unwrap();
|
||||||
|
|
||||||
|
let (clone1_dir, clone1) =
|
||||||
|
repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
|
||||||
|
let clone1_dir = clone1_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
|
write_commit_file(&clone1, "test.txt", "test", "commit1");
|
||||||
|
push_branch(
|
||||||
|
&clone1_dir.into(),
|
||||||
|
"origin",
|
||||||
|
"master",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (clone2_dir, _clone2) =
|
||||||
|
repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
|
||||||
|
let clone2_dir = clone2_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
|
let branches =
|
||||||
|
get_branches_info(&clone2_dir.into(), false).unwrap();
|
||||||
|
let head = branches
|
||||||
|
.iter()
|
||||||
|
.find(|b| b.name == "origin/HEAD")
|
||||||
|
.expect("origin/HEAD");
|
||||||
|
|
||||||
|
assert!(checkout_remote_branch(&clone2_dir.into(), head).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_checkout_remote_branch_hierarchical() {
|
fn test_checkout_remote_branch_hierarchical() {
|
||||||
let (r1_dir, _repo) = repo_init_bare().unwrap();
|
let (r1_dir, _repo) = repo_init_bare().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ use asyncgit::{
|
||||||
sync::{
|
sync::{
|
||||||
self,
|
self,
|
||||||
branch::{
|
branch::{
|
||||||
checkout_remote_branch, BranchDetails, LocalBranch,
|
checkout_remote_branch, is_remote_head_ref, BranchDetails,
|
||||||
|
LocalBranch,
|
||||||
RemoteBranch,
|
RemoteBranch,
|
||||||
},
|
},
|
||||||
checkout_branch, get_branches_info,
|
checkout_branch, get_branches_info,
|
||||||
|
|
@ -317,12 +318,9 @@ impl BranchListPopup {
|
||||||
self.check_remotes();
|
self.check_remotes();
|
||||||
self.branches =
|
self.branches =
|
||||||
get_branches_info(&self.repo.borrow(), self.local)?;
|
get_branches_info(&self.repo.borrow(), self.local)?;
|
||||||
//remove remote branch called `HEAD`
|
|
||||||
if !self.local {
|
if !self.local {
|
||||||
self.branches
|
self.branches
|
||||||
.iter()
|
.retain(|b| !is_remote_head_ref(&b.name));
|
||||||
.position(|b| b.name.ends_with("/HEAD"))
|
|
||||||
.map(|idx| self.branches.remove(idx));
|
|
||||||
}
|
}
|
||||||
self.set_selection(self.selection)?;
|
self.set_selection(self.selection)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue