Skip to content

Commit 1a86528

Browse files
committed
fix(submit): run in correct repo working copy directory when in a worktree
1 parent a7b4278 commit 1a86528

File tree

4 files changed

+102
-14
lines changed

4 files changed

+102
-14
lines changed

git-branchless-lib/src/git/repo.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -516,8 +516,42 @@ impl Repo {
516516

517517
/// Get the path to the working copy for this repository. If the repository
518518
/// is bare (has no working copy), returns `None`.
519-
pub fn get_working_copy_path(&self) -> Option<&Path> {
520-
self.inner.workdir()
519+
pub fn get_working_copy_path(&self) -> Option<PathBuf> {
520+
let workdir = self.inner.workdir()?;
521+
if !self.inner.is_worktree() {
522+
return Some(workdir.to_owned());
523+
}
524+
525+
// Under some circumstances (not sure exactly when),
526+
// `git2::Repository::workdir` on a worktree returns a path like
527+
// `/path/to/repo/.git/worktrees/worktree-name/` instead of
528+
// `/path/to/worktree/`.
529+
let gitdir_file = workdir.join("gitdir");
530+
let gitdir = match std::fs::read_to_string(&gitdir_file) {
531+
Ok(gitdir) => gitdir,
532+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
533+
return Some(workdir.to_path_buf());
534+
}
535+
Err(err) => {
536+
warn!(
537+
?workdir,
538+
?gitdir_file,
539+
?err,
540+
"gitdir file for worktree could not be read; cannot get workdir path"
541+
);
542+
return None;
543+
}
544+
};
545+
let gitdir = match gitdir.strip_suffix('\n') {
546+
Some(gitdir) => gitdir,
547+
None => gitdir.as_str(),
548+
};
549+
let gitdir = PathBuf::from(gitdir);
550+
let workdir = gitdir.parent()?; // remove `.git` suffix
551+
std::fs::canonicalize(workdir).ok().or_else(|| {
552+
warn!(?workdir, "Failed to canonicalize workdir");
553+
None
554+
})
521555
}
522556

523557
/// Get the index file for this repository.
@@ -1469,17 +1503,18 @@ impl Repo {
14691503
let repo_path = self
14701504
.get_working_copy_path()
14711505
.ok_or(Error::NoWorkingCopyPath)?;
1506+
let repo_path = &repo_path;
14721507
let new_tree_entries: HashMap<PathBuf, Option<(NonZeroOid, FileMode)>> = match opts {
14731508
AmendFastOptions::FromWorkingCopy { status_entries } => status_entries
14741509
.iter()
14751510
.flat_map(|entry| {
14761511
entry.paths().into_iter().map(
14771512
move |path| -> Result<(PathBuf, Option<(NonZeroOid, FileMode)>)> {
1478-
let file_path = &repo_path.join(&path);
1513+
let file_path = repo_path.join(&path);
14791514
// Try to create a new blob OID based on the current on-disk
14801515
// contents of the file in the working copy.
14811516
let entry = self
1482-
.create_blob_from_path(file_path)?
1517+
.create_blob_from_path(&file_path)?
14831518
.map(|oid| (oid, entry.working_copy_file_mode));
14841519
Ok((path, entry))
14851520
},

git-branchless-lib/src/git/run.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::convert::TryInto;
33
use std::ffi::{OsStr, OsString};
44
use std::fmt::Write;
55
use std::io::{BufRead, BufReader, Read, Write as WriteIo};
6-
use std::path::{Path, PathBuf};
6+
use std::path::PathBuf;
77
use std::process::{Command, ExitStatus, Stdio};
88
use std::sync::Arc;
99
use std::thread::{self, JoinHandle};
@@ -256,7 +256,7 @@ impl GitRunInfo {
256256
///
257257
/// This contains additional logic for symlinked `.git` directories to work
258258
/// around an upstream `libgit2` issue.
259-
fn working_directory<'a>(&'a self, repo: &'a Repo) -> &'a Path {
259+
fn working_directory<'a>(&'a self, repo: &'a Repo) -> PathBuf {
260260
repo.get_working_copy_path()
261261
// `libgit2` returns the working copy path as the parent of the
262262
// `.git` directory. However, if the `.git` directory is a symlink,
@@ -274,13 +274,13 @@ impl GitRunInfo {
274274
// root of the working tree.
275275
.map(|working_copy| {
276276
// Both paths are already "canonicalized".
277-
if !self.working_directory.starts_with(working_copy) {
278-
&self.working_directory
277+
if !self.working_directory.starts_with(&working_copy) {
278+
self.working_directory.clone()
279279
} else {
280280
working_copy
281281
}
282282
})
283-
.unwrap_or_else(|| repo.get_path())
283+
.unwrap_or_else(|| repo.get_path().to_owned())
284284
}
285285

286286
fn run_silent_inner(

git-branchless-lib/tests/test_repo.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
use std::path::PathBuf;
2+
13
use branchless::git::{
2-
AmendFastOptions, BranchType, CherryPickFastOptions, FileMode, FileStatus, GitVersion,
4+
AmendFastOptions, BranchType, CherryPickFastOptions, FileMode, FileStatus, GitVersion, Repo,
35
StatusEntry,
46
};
5-
use branchless::testing::make_git;
7+
use branchless::testing::{make_git, make_git_worktree, GitWorktreeWrapper};
68

79
#[test]
810
fn test_parse_git_version_output() {
@@ -262,3 +264,42 @@ fn test_branch_debug() -> eyre::Result<()> {
262264

263265
Ok(())
264266
}
267+
268+
#[test]
269+
fn test_worktree_working_copy_path() -> eyre::Result<()> {
270+
let git = make_git()?;
271+
git.init_repo()?;
272+
git.commit_file("test1", 1)?;
273+
274+
let GitWorktreeWrapper { temp_dir, worktree } = make_git_worktree(&git, "new-worktree")?;
275+
{
276+
let stdout = worktree.smartlog()?;
277+
insta::assert_snapshot!(stdout, @r###"
278+
:
279+
@ 62fc20d (master) create test1.txt
280+
"###);
281+
}
282+
283+
fn canonicalize(path: Option<PathBuf>) -> PathBuf {
284+
match path {
285+
None => PathBuf::from("<none>"),
286+
Some(path) => {
287+
// On macOS, it looks like the temporary directory has to be
288+
// canonicalized as it may be `/var` vs. `/private/var`.
289+
std::fs::canonicalize(path).unwrap_or_else(|err| format!("<error: {err}>").into())
290+
}
291+
}
292+
}
293+
let worktree_repo = worktree.get_repo()?;
294+
assert_eq!(
295+
canonicalize(worktree_repo.get_working_copy_path()),
296+
canonicalize(Some(temp_dir.path().join("new-worktree")))
297+
);
298+
let directly_opened_worktree_repo = Repo::from_dir(&temp_dir.path().join("new-worktree"))?;
299+
assert_eq!(
300+
canonicalize(directly_opened_worktree_repo.get_working_copy_path()),
301+
canonicalize(worktree_repo.get_working_copy_path())
302+
);
303+
304+
Ok(())
305+
}

git-branchless-submit/src/lib.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use git_branchless_opts::{
3838
};
3939
use git_branchless_revset::resolve_commits;
4040
use phabricator::PhabricatorForge;
41-
use tracing::{debug, warn};
41+
use tracing::{debug, info, instrument, warn};
4242

4343
lazy_static! {
4444
/// The style for branches which were successfully submitted.
@@ -473,6 +473,7 @@ create and push them, retry this operation with the --create option."
473473
Ok(Ok(()))
474474
}
475475

476+
#[instrument]
476477
fn select_forge<'a>(
477478
effects: &'a Effects,
478479
git_run_info: &'a GitRunInfo,
@@ -484,10 +485,20 @@ fn select_forge<'a>(
484485
forge: Option<ForgeKind>,
485486
) -> Box<dyn Forge + 'a> {
486487
let forge_kind = match forge {
487-
Some(forge_kind) => forge_kind,
488+
Some(forge_kind) => {
489+
info!(?forge_kind, "Forge kind was explicitly set");
490+
forge_kind
491+
}
488492
None => {
489493
let use_phabricator = if let Some(working_copy_path) = repo.get_working_copy_path() {
490-
working_copy_path.join(".arcconfig").is_file()
494+
let arcconfig_path = &working_copy_path.join(".arcconfig");
495+
let arcconfig_present = arcconfig_path.is_file();
496+
debug!(
497+
?arcconfig_path,
498+
?arcconfig_present,
499+
"Checking arcconfig path to decide whether to use Phabricator"
500+
);
501+
arcconfig_present
491502
} else {
492503
false
493504
};
@@ -499,6 +510,7 @@ fn select_forge<'a>(
499510
}
500511
};
501512

513+
info!(?forge_kind, "Selected forge kind");
502514
match forge_kind {
503515
ForgeKind::Branch => Box::new(BranchForge {
504516
effects,

0 commit comments

Comments
 (0)