Skip to content

Commit 506ac9c

Browse files
author
Test User
committed
fix: improve repository name detection for various git configurations
1 parent 8bd2d2d commit 506ac9c

2 files changed

Lines changed: 433 additions & 97 deletions

File tree

src/infrastructure/git.rs

Lines changed: 167 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -295,65 +295,47 @@ impl GitWorktreeManager {
295295
/// For repositories with many worktrees, this significantly reduces the
296296
/// total time compared to sequential processing.
297297
pub fn list_worktrees(&self) -> Result<Vec<WorktreeInfo>> {
298-
let mut worktrees;
298+
let mut worktrees = Vec::new();
299299
let worktree_names = self.repo.worktrees()?;
300300

301-
// Use parallel processing for better performance
302-
use std::sync::{Arc, Mutex};
303-
use std::thread;
304-
305-
let results = Arc::new(Mutex::new(Vec::new()));
306-
let mut handles = vec![];
307-
308301
for name in worktree_names.iter().flatten() {
309302
if let Ok(worktree) = self.repo.find_worktree(name) {
310303
let path = worktree.path();
311304
let is_current = self.is_current_worktree(path);
312-
let name_clone = name.to_string();
313-
let path_clone = path.to_path_buf();
314305
let is_locked = worktree.is_locked().is_ok();
315-
let results_clone = Arc::clone(&results);
316-
317-
// Spawn thread for each worktree to parallelize repository operations
318-
let handle = thread::spawn(move || {
319-
let branch = if let Ok(wt_repo) = Repository::open(&path_clone) {
320-
wt_repo
321-
.head()
322-
.ok()
323-
.and_then(|h| h.shorthand().map(|s| s.to_string()))
324-
.unwrap_or_else(|| String::from(DEFAULT_BRANCH_DETACHED))
325-
} else {
326-
String::from(DEFAULT_BRANCH_UNKNOWN)
327-
};
328306

329-
// Get additional status info for the worktree
330-
let worktree_status = get_worktree_status(&path_clone);
331-
332-
let info = WorktreeInfo {
333-
name: name_clone,
334-
path: path_clone,
335-
branch,
336-
is_locked,
337-
is_current,
338-
has_changes: worktree_status.has_changes,
339-
last_commit: worktree_status.last_commit,
340-
ahead_behind: worktree_status.ahead_behind,
341-
};
307+
let branch = if let Ok(wt_repo) = Repository::open(path) {
308+
if let Ok(head) = wt_repo.head() {
309+
if let Some(shorthand) = head.shorthand() {
310+
shorthand.to_string()
311+
} else {
312+
String::from(DEFAULT_BRANCH_DETACHED)
313+
}
314+
} else {
315+
String::from(DEFAULT_BRANCH_DETACHED)
316+
}
317+
} else {
318+
String::from(DEFAULT_BRANCH_UNKNOWN)
319+
};
342320

343-
results_clone.lock().unwrap().push(info);
344-
});
321+
// Get additional status info for the worktree
322+
let worktree_status = get_worktree_status(path);
323+
324+
let info = WorktreeInfo {
325+
name: name.to_string(),
326+
path: path.to_path_buf(),
327+
branch,
328+
is_locked,
329+
is_current,
330+
has_changes: worktree_status.has_changes,
331+
last_commit: worktree_status.last_commit,
332+
ahead_behind: worktree_status.ahead_behind,
333+
};
345334

346-
handles.push(handle);
335+
worktrees.push(info);
347336
}
348337
}
349338

350-
// Wait for all threads to complete
351-
for handle in handles {
352-
handle.join().unwrap();
353-
}
354-
355-
worktrees = Arc::try_unwrap(results).unwrap().into_inner().unwrap();
356-
357339
// Sort by name for consistent ordering
358340
worktrees.sort_by(|a, b| a.name.cmp(&b.name));
359341

@@ -370,9 +352,107 @@ impl GitWorktreeManager {
370352
///
371353
/// `true` if the current working directory is within the given worktree path
372354
fn is_current_worktree(&self, path: &std::path::Path) -> bool {
373-
std::env::current_dir()
374-
.map(|dir| dir.starts_with(path))
375-
.unwrap_or(false)
355+
let current_dir = match std::env::current_dir() {
356+
Ok(dir) => dir,
357+
Err(_) => return false,
358+
};
359+
360+
// Try to canonicalize both paths for accurate comparison
361+
match (current_dir.canonicalize(), path.canonicalize()) {
362+
(Ok(current_canonical), Ok(path_canonical)) => {
363+
// Both paths canonicalized successfully - use canonical comparison
364+
current_canonical.starts_with(&path_canonical)
365+
}
366+
_ => {
367+
// Canonicalization failed for one or both paths
368+
// Fallback to absolute path comparison
369+
let current_absolute = if current_dir.is_absolute() {
370+
current_dir
371+
} else {
372+
// Convert relative path to absolute
373+
match std::env::current_dir().map(|cwd| cwd.join(&current_dir)) {
374+
Ok(abs_path) => abs_path,
375+
Err(_) => current_dir,
376+
}
377+
};
378+
379+
let path_absolute = if path.is_absolute() {
380+
path.to_path_buf()
381+
} else {
382+
// Convert relative path to absolute from repository base
383+
let repo_base = if let Some(workdir) = self.repo.workdir() {
384+
workdir.to_path_buf()
385+
} else {
386+
// For bare repositories, use the repository path
387+
self.repo.path().to_path_buf()
388+
};
389+
repo_base.join(path)
390+
};
391+
392+
// Normalize paths by removing redundant components
393+
let current_normalized = self.normalize_path(&current_absolute);
394+
let path_normalized = self.normalize_path(&path_absolute);
395+
396+
current_normalized.starts_with(&path_normalized)
397+
}
398+
}
399+
}
400+
401+
/// Normalizes a path by removing redundant components like "." and ".."
402+
///
403+
/// This method provides a cross-platform way to normalize paths when
404+
/// canonicalization fails (e.g., for non-existent paths or due to permissions).
405+
///
406+
/// # Arguments
407+
///
408+
/// * `path` - The path to normalize
409+
///
410+
/// # Returns
411+
///
412+
/// A normalized PathBuf with redundant components removed
413+
fn normalize_path(&self, path: &std::path::Path) -> std::path::PathBuf {
414+
use std::path::Component;
415+
416+
let mut normalized = std::path::PathBuf::new();
417+
418+
for component in path.components() {
419+
match component {
420+
Component::Prefix(..) => normalized.push(component),
421+
Component::RootDir => normalized.push(component),
422+
Component::CurDir => {
423+
// Skip "." components
424+
}
425+
Component::ParentDir => {
426+
// Handle ".." by removing the last component if possible
427+
if let Some(last_component) = normalized.components().next_back() {
428+
match last_component {
429+
Component::Prefix(..) | Component::RootDir => {
430+
// Cannot go up from root or prefix
431+
normalized.push(component);
432+
}
433+
Component::ParentDir => {
434+
// Multiple ".." in sequence
435+
normalized.push(component);
436+
}
437+
Component::Normal(_) => {
438+
// Remove the previous normal component
439+
normalized.pop();
440+
}
441+
Component::CurDir => {
442+
// Should not happen since we skip CurDir above
443+
normalized.push(component);
444+
}
445+
}
446+
} else {
447+
// No components to remove, keep the ".."
448+
normalized.push(component);
449+
}
450+
}
451+
Component::Normal(_) => normalized.push(component),
452+
}
453+
}
454+
455+
normalized
376456
}
377457

378458
/// Gets the current branch name for a worktree
@@ -1700,4 +1780,42 @@ mod tests {
17001780

17011781
Ok(())
17021782
}
1783+
1784+
#[test]
1785+
fn test_normalize_path_basic() -> Result<()> {
1786+
if let Ok(manager) = GitWorktreeManager::new() {
1787+
let test_path = std::path::Path::new("/home/user/./project/../project/src");
1788+
let normalized = manager.normalize_path(test_path);
1789+
let expected = std::path::PathBuf::from("/home/user/project/src");
1790+
assert_eq!(normalized, expected);
1791+
}
1792+
Ok(())
1793+
}
1794+
1795+
#[test]
1796+
fn test_normalize_path_with_parent_dirs() -> Result<()> {
1797+
if let Ok(manager) = GitWorktreeManager::new() {
1798+
let test_path = std::path::Path::new("/home/user/project/../../user/project");
1799+
let normalized = manager.normalize_path(test_path);
1800+
let expected = std::path::PathBuf::from("/home/user/project");
1801+
assert_eq!(normalized, expected);
1802+
}
1803+
Ok(())
1804+
}
1805+
1806+
#[test]
1807+
fn test_is_current_worktree_same_path() -> Result<()> {
1808+
if let Ok(manager) = GitWorktreeManager::new() {
1809+
// Test with current directory
1810+
if let Ok(current_dir) = std::env::current_dir() {
1811+
// Current directory should match itself
1812+
assert!(manager.is_current_worktree(&current_dir));
1813+
1814+
// A subdirectory of current should not match
1815+
let subdir = current_dir.join("subdir");
1816+
assert!(!manager.is_current_worktree(&subdir));
1817+
}
1818+
}
1819+
Ok(())
1820+
}
17031821
}

0 commit comments

Comments
 (0)