@@ -3,7 +3,7 @@ use std::convert::TryInto;
3
3
use std:: ffi:: { OsStr , OsString } ;
4
4
use std:: fmt:: Write ;
5
5
use std:: io:: { BufRead , BufReader , Read , Write as WriteIo } ;
6
- use std:: path:: PathBuf ;
6
+ use std:: path:: { Component , Path , PathBuf } ;
7
7
use std:: process:: { Command , ExitStatus , Stdio } ;
8
8
use std:: sync:: Arc ;
9
9
use std:: thread:: { self , JoinHandle } ;
@@ -243,6 +243,48 @@ impl GitRunInfo {
243
243
}
244
244
}
245
245
246
+ /// Returns the working directory for commands run on a given `Repo`.
247
+ ///
248
+ /// This is typically the working copy path for the repo.
249
+ ///
250
+ /// Some commands (notably `git status`) do not function correctly when run
251
+ /// from the git repo (i.e. `.git`) path.
252
+ /// Hooks should also be run from the working copy path - from
253
+ /// `githooks(5)`: Before Git invokes a hook, it changes its working
254
+ /// directory to either $GIT_DIR in a bare repository or the root of the
255
+ /// working tree in a non-bare repository.
256
+ ///
257
+ /// This contains additional logic for `repo`-managed projects to work
258
+ /// around an upstream `libgit2` issue.
259
+ fn working_directory < ' a > ( & ' a self , repo : & ' a Repo ) -> & ' a Path {
260
+ repo. get_working_copy_path ( )
261
+ // `libgit2` returns the working copy path as the parent of the
262
+ // `.git` directory. However, if the `.git` directory is a symlink,
263
+ // `libgit2` *resolves the symlink*, returning an incorrect working
264
+ // directory: https://github.com/libgit2/libgit2/issues/6401
265
+ //
266
+ // This notably occurs when working with `repo`-managed projects,
267
+ // which symlinks `.git` directories to a subdirectory of a shared
268
+ // `.repo` directory. To work around this bug, we instead use the
269
+ // current working directory when we detect a `repo`-managed
270
+ // project.
271
+ //
272
+ // This workaround may result in slightly incorrect hook behavior
273
+ // for `repo`-managed projects, as the current working directory may
274
+ // be a subdirectory of the root of the working tree.
275
+ . map ( |working_copy| {
276
+ if working_copy
277
+ . components ( )
278
+ . contains ( & Component :: Normal ( OsStr :: new ( ".repo" ) ) )
279
+ {
280
+ & self . working_directory
281
+ } else {
282
+ working_copy
283
+ }
284
+ } )
285
+ . unwrap_or_else ( || repo. get_path ( ) )
286
+ }
287
+
246
288
fn run_silent_inner (
247
289
& self ,
248
290
repo : & Repo ,
@@ -260,12 +302,7 @@ impl GitRunInfo {
260
302
stdin,
261
303
} = opts;
262
304
263
- // Prefer running in the working copy path to the repo path, because
264
- // some commands (notably `git status`) to not function correctly
265
- // when run from the git repo (i.e. `.git`) path.
266
- let repo_path = repo
267
- . get_working_copy_path ( )
268
- . unwrap_or_else ( || repo. get_path ( ) ) ;
305
+ let repo_path = self . working_directory ( repo) ;
269
306
// Technically speaking, we should be able to work with non-UTF-8 repository
270
307
// paths. Need to make the typechecker accept it.
271
308
let repo_path = repo_path. to_str ( ) . ok_or_else ( || {
@@ -383,13 +420,7 @@ impl GitRunInfo {
383
420
384
421
if hook_dir. join ( hook_name) . exists ( ) {
385
422
let mut child = Command :: new ( get_sh ( ) . ok_or_else ( || eyre ! ( "could not get sh" ) ) ?)
386
- // From `githooks(5)`: Before Git invokes a hook, it changes its
387
- // working directory to either $GIT_DIR in a bare repository or the
388
- // root of the working tree in a non-bare repository.
389
- . current_dir (
390
- repo. get_working_copy_path ( )
391
- . unwrap_or_else ( || repo. get_path ( ) ) ,
392
- )
423
+ . current_dir ( self . working_directory ( repo) )
393
424
. arg ( "-c" )
394
425
. arg ( format ! ( "{hook_name} \" $@\" " ) )
395
426
. arg ( hook_name) // "$@" expands "$1" "$2" "$3" ... but we also must specify $0.
0 commit comments