955

Mercurial has a way of printing the root directory (that contains .hg) via

hg root

Is there something equivalent in git to get the directory that contains the .git directory?

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
wojo
  • 11,061
  • 4
  • 22
  • 21
  • Also for anyone curious or searching `bzr root` was used a lot in Bazaar – Kristopher Ives Jun 16 '12 at 20:27
  • Good script Emil. I make the script available online, and allowed the possibility to add a file/directory as argument. http://github.com/Dieterbe/git-scripts/commit/3b7be5e25052e99d9be8ca7d9e6d5c91f8c2a14a – Dieter_be Aug 11 '10 at 14:52
  • Please take note of *'gcd' : Git-Aware 'cd' Relative to Repository Root with Auto-Completion* at http://jeetworks.org/node/52. – Micha Wiedenmann Aug 01 '13 at 09:57
  • 3
    Consider [my answer below `git rev-parse --git-dir`](http://stackoverflow.com/a/958125/6309), as explained [in this comment](http://stackoverflow.com/questions/957928/is-there-a-way-to-get-the-git-root-directory-in-one-command/958125?noredirect=1#comment28043274_958125) – VonC Sep 24 '13 at 14:59
  • 1
    @MichaWiedenmann: the link is dead. – d33tah Aug 24 '15 at 15:18
  • 1
    @d33tah archive link https://web.archive.org/web/20130526035714/http://jeetworks.org/node/52 – adam_0 Dec 17 '15 at 23:45

23 Answers23

1597

Yes:

git rev-parse --show-toplevel

If you want to replicate the Mercurial command more directly, you can create an alias:

git config --global alias.root 'rev-parse --show-toplevel'

and now git root will function just as hg root.


Note: In a submodule this will display the root directory of the submodule and not the parent repository. If you are using Git >=2.13 or above, there is a way that submodules can show the superproject's root directory. If your git is older than that, see this other answer.

orion elenzil
  • 4,484
  • 3
  • 37
  • 49
baudtack
  • 29,062
  • 9
  • 53
  • 61
  • 1
    This is all I was able to find too; may want to alias it to git-top or something similar. – John Bellone Jun 05 '09 at 20:40
  • This gives me the path to the root of the top-level/root directory, which is exactly what I was looking for. – wojo Jun 05 '09 at 21:10
  • 191
    I always define `git config --global alias.exec '!exec '` so I can do things like `git exec make`. This works because shell aliases are always executed in the top-level directory. – Daniel Brockman Nov 01 '11 at 19:09
  • 8
    This is what `hg root` does. It prints out the top-level directory of your checked out repository. It doesn't switch you to it (and it couldn't, in fact, do so because of how the whole concept of current directory and your shell interact). – Omnifarious Feb 08 '12 at 19:25
  • 15
    Smal caveat with this solution - it'll follow up any symbolic links. So, if you are in `~/my.proj/foo/bar`, and `~/my.proj` is symlinked to `~/src/my.proj`, the above command will move you to `~/src/my.proj`. Could be a problem, if whatever you want to do after that is not tree agnostic. – Franci Penov Jul 06 '12 at 19:11
  • Thanks for pointing this out; without your help, it'd be hard for me to guess to look into `git-rev-parse` -- because of its name suggesting it's about processing revision specifications. BTW, I'd be glad to see `git --work-tree` work similar to `git --exec-path[=]`: "If no path is given, git will print the current setting"; at least, IMO, it'd be a logical place to look for such a feature. – imz -- Ivan Zakharyaschev Dec 04 '12 at 12:53
  • 3
    How can I use this from within a git hook? Specifically, I'm doing a post-merge hook and I need to get the actual root directory of the local git repo. – Derek Jul 31 '13 at 16:52
  • The `git exec` alias can be combined with `pwd` to print the root directory: `git exec pwd`.Or sometimes, it's useful to have a subshell at the root temporarily, and then just exit to get back to the original task. – jdsumsion Aug 20 '13 at 17:38
  • @jondavidjohn see https://gist.github.com/hilbix/7724772 `git config --global alias.top '!f() { GIT_TOP="${GIT_DIR%%/.git/modules/*}"; [ ".$GIT_TOP" != ".$GIT_DIR" ] && cd "$GIT_TOP"; exec "$@"; }; f'` and then `git top pwd` – Tino Dec 01 '13 at 10:43
  • 17
    This solution (and many others on this page that I've tried) do not work inside a git hook. To be more precise, it doesn't work when you're inside the project's `.git` directory. – Dennis Dec 23 '14 at 12:15
  • 2
    this `git rev-parse && cd "$(git rev-parse --show-cdup)` doesn't work in the general case since `git rev-parse --show-cdup` returns the empty string in case you are allready in the git root dir. Concequence is you go to your home. – Peter Jan 27 '16 at 10:17
  • 1
    In Windows you'd get reversed slashes - use something like this to reverse them: `git rev-parse --show-toplevel > %TEMP%\GITROOT.TXT & SET /p BranchRoot=<%TEMP%\GITROOT.TXT & SET BranchRoot=%BranchRoot:/=\%` – Ohad Schneider Apr 06 '16 at 15:00
  • 2
    As @Dennis notes, this doesn't work if inside the `.git` directory (or an external `$GIT_DIR` of a repository. See my answer for one that works anywhere in a repository, and fails gracefully in an external `$GIT_DIR` – Tom Hale Aug 09 '16 at 13:35
  • 2
    what a travesty that the command is named that, wtf is "rev-parse"...reverse parse? "hg root" makes so much sense – Alexander Mills Nov 24 '16 at 05:43
  • 1
    @AlexanderMills it probably is "revision parse", but still strange to have this options there. – user85421 Mar 31 '17 at 08:54
  • 1
    @AlexanderMills It appears "rev-parse" gets its name from originally being a helper for "rev-list" (revision list). From the man page for `git-rev-parse`: "Many Git porcelainish commands take mixture of flags and parameters meant for the underlying git `rev-list` command they use internally and flags and parameters for the other commands they use downstream of `git rev-list`. This command is used to distinguish between them." – Randall Sep 08 '18 at 14:11
  • @jondavidjohn wish I saw that comment earlier. Thought I was losing my mind. – Daniel Storm Feb 11 '19 at 22:52
  • I don't think it's correct to say it "doesn't work" in a submodule, as getting the root directory of the submodule is exactly what I'd hope for. (my justification: wouldn't a shell script using this command always want the root directory of its own repository, and not a parent repository?) I submitted an edit which I feel adds a more helpful explanation. – KernelDeimos Mar 01 '19 at 18:25
  • To get it to work from the .git/hooks folder just `cd ../../;git rev-parse --show-top level` – E. Sundin Mar 29 '19 at 15:00
  • this solution doesn't work it some subdirectories of the root. for example in the root it works in the root/src it works, in root/src/tests it suddenly starts appending "/src/tests" to the end – masukomi Feb 03 '20 at 20:02
  • 1
    @E. Sundin Hooks are executed from the repo root by default, no need to care about that if you meant that. – bloody Nov 30 '20 at 18:43
  • This does not work in a worktree (depending on what you want). It reports the root of the worktree rather than the original worktree. So you can't use it to find the main `.git` directory. – Timmmm Aug 12 '21 at 08:17
  • Useful note: you can set it to a bash variable with something like `ROOT=${ROOT:-$(git rev-parse --show-toplevel)}` – Elijas Dapšauskas Jun 06 '22 at 18:57
  • I often use `gcd` to go to project home as analogous to `cd` which takes to user home dir. in my `.bashrc` `alias gcd='cd $(git rev-parse --show-toplevel)'` – Hritik Dec 27 '22 at 13:45
113

Has --show-toplevel only recently been added to git rev-parse or why is nobody mentioning it?

From the git rev-parse man page:

   --show-toplevel
       Show the absolute path of the top-level directory.
marc.guenther
  • 2,761
  • 2
  • 23
  • 18
  • 1
    Thanks for pointing this out; without your help, it'd be hard for me to guess to look into `git-rev-parse` -- because of its name suggesting it's about processing revision specifications. BTW, I'd be glad to see `git --work-tree` work similar to `git --exec-path[=]`: "If no path is given, git will print the current setting"; at least, IMO, it'd be a logical place to look for such a feature. – imz -- Ivan Zakharyaschev Dec 04 '12 at 12:52
  • 4
    In particular, you can alias `root = rev-parse --show-toplevel` in your gitconfig. – Mechanical snail May 27 '13 at 02:39
  • 2
    in other words, you can do `git config --global alias.root "rev-parse --show-toplevel"` and then `git root` will be able to do the job – nonopolarity May 03 '14 at 09:16
  • @RyanTheLeach `git rev-parse --show-toplevel` works when I tried it in a submodule. It prints the root dir of the git submodule. What does it print for you? – wisbucky Nov 06 '18 at 23:16
  • 5
    I was confused why this answer duplicated the top answer. Turns out that the top answer was edited from `--show-cdup` to `--show-top-level` in Feb 2011 (after this answer was submitted). – wisbucky Nov 06 '18 at 23:17
  • @wisbucky I was expecting the root directory of the container, aka the TOP LEVEL not just the nearest kinda top level. – Ryan Leach Nov 06 '18 at 23:31
  • Yes, it has only recently been added. I have access to a system with an older git that lacks it. It’s kinda annoying that the other answer was edited destructively. – mirabilos Aug 19 '21 at 04:56
108

The man page for git-config (under Alias) says:

If the alias expansion is prefixed with an exclamation point, it will be treated as a shell command. [...] Note that shell commands will be executed from the top-level directory of a repository, which may not necessarily be the current directory.

So, on UNIX you can do:

git config --global --add alias.root '!pwd'
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Scott Lindsay
  • 1,283
  • 1
  • 8
  • 3
  • it's the same as --rev-parse show-toplevel, but more elegant! – yhager Nov 05 '10 at 15:39
  • 2
    So, if you have a "git root" command, how can you put that in an alias? If I put it in my `.zshrc`, and I define `alias cg="cd $(git root)", the $() part gets evaluated at source-time, and always points to ~/dotfiles, as that is where my zshrc is. – zelk Dec 01 '12 at 07:36
  • 2
    @cormacrelf You don't put it in a shell alias. You can put it in a shell function or script. – Conrad Meyer Dec 03 '12 at 19:43
  • this doesn't work when you are anywhere in the `.git/` directory. Then it returns your root directory as well as wherever you are in `.git/` – Ehtesh Choudhury Feb 22 '13 at 23:58
  • 5
    @cormacrelf Put it in single quotes instead of double quotes, then it will not be expanded at definition time, but at runtime. – clacke Apr 14 '13 at 19:59
  • It also fails if you're not in a Git repo. – Mechanical snail May 27 '13 at 02:35
  • 3
    @Mechanicalsnail Really? So what would you expect it to do outside repo? – Alois Mahdal Sep 25 '13 at 17:03
  • 1
    @AloisMahdal actually for me it does not fail outside a repo, it simply reports the cwd. – justinpitts Jan 06 '14 at 21:39
  • @Omnifarious I personally don't know anyone who uses `command.com` (`cmd`) to make commits, but on Windows I guess you'd want `git config --add alias.root '!cd'` or alternatively `'!echo %CD%'`. Accepted answer, however, is best if you need cross-platform compatibility. – Seldom 'Where's Monica' Needy Feb 04 '16 at 01:31
  • clever. also works with symlinks better than accepted answer – Grozz Apr 28 '16 at 08:03
  • @zelk use single quotes, not double quotes. Double-quoted strings are evaluated at alias definition time. – mirabilos Aug 19 '21 at 04:37
  • A variation on this, if you don't want to modify a global alias: `git -c alias.root=!pwd root` – Markus Jarderot May 30 '23 at 10:47
95

How about "git rev-parse --git-dir" ?

F:\prog\git\test\copyMerge\dirWithConflicts>git rev-parse --git-dir
F:/prog/git/test/copyMerge/.git

The --git-dir option seems to work.

It does work even in a bare repository, while git rev-parse --show-toplevel would trigger (in said bare repository) a "fatal: this operation must be run in a work tree".

From git rev-parse manual page:

--git-dir

    Show $GIT_DIR if defined else show the path to the .git directory.

You can see it in action in this git setup-sh script.

If you are in a submodule folder, with Git >=2.13, use:

git rev-parse --show-superproject-working-tree

If you are using git rev-parse --show-toplevel, make sure it is with Git 2.25+ (Q1 2020).

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Exactly what I was looking for, since it gives the absolute path. I tried to make an alias that will also cd into this directory, but that doesn't work with "cdroot = !cd $(git rev-parse --git-dir)" sadly. – wojo Jun 05 '09 at 21:05
  • 4
    Oh wait, this was close but it gets the actual .git dir, not the base of the git repo. Also, the .git directory could be elsewhere, so this isn't what I was looking for exactly. – wojo Jun 05 '09 at 21:08
  • 2
    Right, I see now what you were actually looking for. --show-cdup is more appropriate then. I leave my answer for illustrating the difference between the two options. – VonC Jun 05 '09 at 21:19
  • 2
    It also appears that this command gives a relative path for `.git` if you're already in the root directory. (At least, it does on msysgit.) – Blair Holloway Jul 04 '10 at 23:34
  • 3
    +1, this is the only answer that answers the original question "get the directory that contains the .git directory?", amusing to see that the OP itself mention "the .git directory might be elsewhere". – FabienAndre Sep 24 '13 at 14:49
  • 1
    This is the only solution that works on windows AND gives a parsable result when using git submodules (e.g. c:\repositories\myrepo\.git\modules\mysubmodule). That makes it the most robust solution especially in a reusable script. – chriskelly Dec 03 '14 at 10:30
  • The full solution along those lines is: `(root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd)` but this doesn't cover external `$GIT_DIR`s which are named other than `.git` For a full solution, see my answer below. – Tom Hale Aug 09 '16 at 14:07
  • 3
    Thanks. I actually wanted the git dir and this works on submodules too, which don't necessarily have a `.git/` folder below their toplevel folder. – joeytwiddle Oct 17 '16 at 06:28
  • @joeytwiddle Nice! 7 years later, and this answer is still relevant! – VonC Oct 17 '16 at 06:30
  • @BlairHolloway There does exist `--path-format=(absolute|relative)` to override the relative behavior. I was not able to answer the question of whether or not this was an option in 2010. – Erich Aug 28 '23 at 21:09
51

To write a simple answer here, so that we can use

git root

to do the job, simply configure your git by using

git config --global alias.root "rev-parse --show-toplevel"

and then you might want to add the following to your ~/.bashrc:

alias cdroot='cd $(git root)'

so that you can just use cdroot to go to the top of your repo.

nonopolarity
  • 146,324
  • 131
  • 460
  • 740
28

If you're already in the top-level or not in a git repository cd $(git rev-parse --show-cdup) will take you home (just cd). cd ./$(git rev-parse --show-cdup) is one way of fixing that.

Karl
  • 698
  • 4
  • 4
  • 10
    Another option is to quote it: `cd "$(git rev-parse --show-cdup)"`. This works because `cd ""` takes you nowhere, rather than back `$HOME`. And it's best practice to quote $() invocations anyway, in case they output something with spaces (not that this command will in this case, though). – ctrueden Apr 17 '15 at 16:35
  • 1
    This answer works well even if you changed directory to your git repo [via a symlink](https://unix.stackexchange.com/questions/180620/understanding-directory-symlinks-traversals-and-the-parent-directory). Some of the other examples don't work as expected when your git repo is under a symlink as they do not resolve the git's root directory relative to bash's `$PWD`. This example will resolve git's root relative to `$PWD` instead of `realpath $PWD`. – Damien Oct 21 '15 at 12:25
21

Short solutions that work with submodules, in hooks, and inside the .git directory

Here's the short answer that most will want:

r=$(git rev-parse --git-dir) && r=$(cd "$r" && pwd)/ && echo "${r%%/.git/*}"

This will work anywhere in a git working tree (including inside the .git directory), but assumes that repository directory(s) are called .git (which is the default). With submodules, this will go to the root of the outermost containing repository.

If you want to get to the root of the current submodule use:

echo $(r=$(git rev-parse --show-toplevel) && ([[ -n $r ]] && echo "$r" || (cd $(git rev-parse --git-dir)/.. && pwd) ))

To easily execute a command in your submodule root, under [alias] in your .gitconfig, add:

sh = "!f() { root=$(pwd)/ && cd ${root%%/.git/*} && git rev-parse && exec \"$@\"; }; f"

This allows you to easily do things like git sh ag <string>

Robust solution that supports differently named or external .git or $GIT_DIR directories.

Note that $GIT_DIR may point somewhere external (and not be called .git), hence the need for further checking.

Put this in your .bashrc:

# Print the name of the git working tree's root directory
function git_root() {
  local root first_commit
  # git displays its own error if not in a repository
  root=$(git rev-parse --show-toplevel) || return
  if [[ -n $root ]]; then
    echo $root
    return
  elif [[ $(git rev-parse --is-inside-git-dir) = true ]]; then
    # We're inside the .git directory
    # Store the commit id of the first commit to compare later
    # It's possible that $GIT_DIR points somewhere not inside the repo
    first_commit=$(git rev-list --parents HEAD | tail -1) ||
      echo "$0: Can't get initial commit" 2>&1 && false && return
    root=$(git rev-parse --git-dir)/.. &&
      # subshell so we don't change the user's working directory
    ( cd "$root" &&
      if [[ $(git rev-list --parents HEAD | tail -1) = $first_commit ]]; then
        pwd
      else
        echo "$FUNCNAME: git directory is not inside its repository" 2>&1
        false
      fi
    )
  else
    echo "$FUNCNAME: Can't determine repository root" 2>&1
    false
  fi
}

# Change working directory to git repository root
function cd_git_root() {
  local root
  root=$(git_root) || return 1  # git_root will print any errors
  cd "$root"
}

Execute it by typing git_root (after restarting your shell: exec bash)

Tom Hale
  • 40,825
  • 36
  • 187
  • 242
  • This code is being code-reviewed at [Robust bash function to find the root of a git repository](http://codereview.stackexchange.com/questions/138255/robust-bash-function-to-find-the-root-of-a-git-repository). Look there for updates. – Tom Hale Aug 09 '16 at 13:23
  • Nice. More complex that what I proposed 7 years ago (http://stackoverflow.com/a/958125/6309), but still +1 – VonC Aug 09 '16 at 13:43
  • 1
    The shorter solution along those lines is: `(root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd)` but this doesn't cover external `$GIT_DIR`s which are named other than `.git` – Tom Hale Aug 09 '16 at 14:00
  • I wonder if this take into account the multiple worktrees that are now possible in git 2.5+ (http://stackoverflow.com/a/30185564/6309) – VonC Aug 09 '16 at 14:05
  • Your comment link to "Robust bash function to find the root ..." now gives a 404. – ErikE Oct 07 '19 at 16:15
  • @ErikE it says `deleted by Community♦ Mar 2 at 0:00 (RemoveDeadQuestions)` – Tom Hale Oct 08 '19 at 05:20
  • Do you have any updates that came out of that code review? – ErikE Oct 08 '19 at 17:39
  • @ErikE sadly not, it was deleted because of no response to the question. Anyways, I feel it's pretty robust, and `shellcheck` clean. – Tom Hale Oct 09 '19 at 06:52
  • Hooks are executed from the repo root by default, no need to care about that within hooks themselves. – bloody Nov 30 '20 at 18:42
18

As others have noted, the core of the solution is to use git rev-parse --show-cdup. However, there are a few of edge cases to address:

  1. When the cwd already is the root of the working tree, the command yields an empty string.
    Actually it produces an empty line, but command substitution strip off the trailing line break. The final result is an empty string.

    Most answers suggest prepending the output with ./ so that an empty output becomes "./" before it is fed to cd.

  2. When GIT_WORK_TREE is set to a location that is not the parent of the cwd, the output may be an absolute pathname.

    Prepending ./ is wrong in this situation. If a ./ is prepended to an absolute path, it becomes a relative path (and they only refer to the same location if the cwd is the root directory of the system).

  3. The output may contain whitespace.

    This really only applies in the second case, but it has an easy fix: use double quotes around the command substitution (and any subsequent uses of the value).

As other answers have noted, we can do cd "./$(git rev-parse --show-cdup)", but this breaks in the second edge case (and the third edge case if we leave off the double quotes).

Many shells treat cd "" as a no-op, so for those shells we could do cd "$(git rev-parse --show-cdup)" (the double quotes protect the empty string as an argument in the first edge case, and preserve whitespace in the third edge case). POSIX says the result of cd "" is unspecified, so it may be best to avoid making this assumption.

A solution that works in all of the above cases requires a test of some sort. Done explicitly, it might look like this:

cdup="$(git rev-parse --show-cdup)" && test -n "$cdup" && cd "$cdup"

No cd is done for the first edge case.

If it is acceptable to run cd . for the first edge case, then the conditional can be done in the expansion of the parameter:

cdup="$(git rev-parse --show-cdup)" && cd "${cdup:-.}"
Chris Johnsen
  • 214,407
  • 26
  • 209
  • 186
17

To calculate the absolute path of the current git root directory, say for use in a shell script, use this combination of readlink and git rev-parse:

gitroot=$(readlink -f ./$(git rev-parse --show-cdup))

git-rev-parse --show-cdup gives you the right number of ".."s to get to the root from your cwd, or the empty string if you are at the root. Then prepend "./" to deal with the empty string case and use readlink -f to translate to a full path.

You could also create a git-root command in your PATH as a shell script to apply this technique:

cat > ~/bin/git-root << EOF
#!/bin/sh -e
cdup=$(git rev-parse --show-cdup)
exec readlink -f ./$cdup
EOF
chmod 755 ~/bin/git-root

(The above can be pasted into a terminal to create git-root and set execute bits; the actual script is in lines 2, 3 and 4.)

And then you'd be able to run git root to get the root of your current tree. Note that in the shell script, use "-e" to cause the shell to exit if the rev-parse fails so that you can properly get the exit status and error message if you are not in a git directory.

Emil Sit
  • 22,894
  • 7
  • 53
  • 75
  • 2
    Your examples will break if the git "root" directory path contains spaces. Always use `"$(git rev-parse ...)"` instead of hacks like `./$(git rev-parse ...)`. – Mikko Rantalainen Mar 29 '17 at 10:48
  • `readlink -f` doesn't work the same on BSD. See [this SO](https://stackoverflow.com/q/1055671/62269) for workarounds. The Python answer will probably work without installing anything: `python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$(git rev-parse --show-cdup)"`. – Bluu May 16 '18 at 00:24
16

Just in case if you're feeding this path to the Git itself, use :/

# this adds the whole working tree from any directory in the repo
git add :/

# and is equal to
git add $(git rev-parse --show-toplevel)
Nick Volynkin
  • 14,023
  • 6
  • 43
  • 67
10

To amend the "git config" answer just a bit:

git config --global --add alias.root '!pwd -P'

and get the path cleaned up. Very nice.

CharlesB
  • 86,532
  • 28
  • 194
  • 218
Bruce K
  • 749
  • 5
  • 15
9

If you're looking for a good alias to do this plus not blow up cd if you aren't in a git dir:

alias ..g='git rev-parse && cd "$(git rev-parse --show-cdup)"'
Dan Heberden
  • 10,990
  • 3
  • 33
  • 29
8

git-extras

adds $ git root
see https://github.com/tj/git-extras/blob/master/Commands.md#git-root

$ pwd
.../very-deep-from-root-directory
$ cd `git root`
$ git add . && git commit

Availability of git-extras

Cœur
  • 37,241
  • 25
  • 195
  • 267
Hotschke
  • 9,402
  • 6
  • 46
  • 53
6

This shell alias works whether you are in a git subdir, or at the top level:

alias gr='[ ! -z `git rev-parse --show-toplevel` ] && cd `git rev-parse --show-toplevel || pwd`'

updated to use modern syntax instead of backticks:

alias gr='[ ! -z $(git rev-parse --show-toplevel) ] && cd $(git rev-parse --show-toplevel || pwd)'
MERM
  • 629
  • 7
  • 21
6

Since Git 2.13.0, it supports a new option to show the path of the root project, which works even when being used from inside a submodule:

git rev-parse --show-superproject-working-tree
  • https://git-scm.com/docs/git-rev-parse#git-rev-parse---show-superproject-working-tree. Interesting. I must have missed that one. +1 – VonC May 16 '18 at 14:39
  • 4
    Warning though: " Outputs nothing if the current repository is not used as a submodule by any project." – VonC May 16 '18 at 14:39
  • Thanks for the comment! I hadn't noticed that. – Jordi Vilalta Prat May 17 '18 at 15:45
  • Also pay attention to nested submodules. – John Siu May 08 '22 at 07:13
  • @VonC, you can add `--show-toplevel` to fall back to git root behaviour. By piping to `tail -n1` or `head -n1` you can grab one or the other depending on what is available. – swalog May 03 '23 at 09:50
  • @swalog Yes, indeed. I believe I mention `--show-toplevel` in [my own answer](https://stackoverflow.com/a/958125/6309). – VonC May 03 '23 at 11:47
5
$ git config alias.root '!pwd'
# then you have:
$ git root
FractalSpace
  • 5,577
  • 3
  • 42
  • 47
  • This alias will fail as a global. Use this instead (in ~/.gitconfig): `[alias] findroot = "!f () { [[ -d ".git" ]] && echo "Found git in [`pwd`]" && exit 0; cd .. && echo "IN `pwd`" && f;}; f"` – FractalSpace Mar 06 '13 at 18:17
  • why downvote? Please highlight any error or suggest improvement instead. – FractalSpace May 27 '13 at 20:26
  • 1
    For me `git config --global alias.root '!pwd'` works. I was unable to spot any case where it acts differently to the non-global variant. (Unix, git 1.7.10.4) BTW: Your `findroot` requires a `/.git` to avoid an endless recursion. – Tino Dec 01 '13 at 09:04
5

Here is a script that I've written that handles both cases: 1) repository with a workspace, 2) bare repository.

https://gist.github.com/jdsumsion/6282953

git-root (executable file in your path):

#!/bin/bash
GIT_DIR=`git rev-parse --git-dir` &&
(
  if [ `basename $GIT_DIR` = ".git" ]; then
    # handle normal git repos (with a .git dir)
    cd $GIT_DIR/..
  else
    # handle bare git repos (the repo IS a xxx.git dir)
    cd $GIT_DIR
  fi
  pwd
)

Hopefully this is helpful.

jdsumsion
  • 14,473
  • 1
  • 15
  • 9
  • 1
    Keith, thank you for the suggestion, I've included the script. – jdsumsion Aug 20 '13 at 17:08
  • I actually upvoted the top answer because I realized the `git exec` idea is more helpful in non-bare repositories. However, this script in my answer handles the bare vs. non-bare case correctly, which could be of use to someone, so I'm leaving this answer here. – jdsumsion Aug 20 '13 at 17:40
  • 1
    However this fails within `git submodule`s where `$GIT_DIR` contains something like `/.git/modules/SUBMODULE`. Also you assume, that the `.git` directory is part of the worktree in the non-bare case. – Tino Dec 01 '13 at 09:14
5
alias git-root='cd \`git rev-parse --git-dir\`; cd ..'

Everything else fails at some point either going to the home directory or just miserably failing. This is the quickest and shortest way to get back to the GIT_DIR.

CharlesB
  • 86,532
  • 28
  • 194
  • 218
tocororo
  • 61
  • 2
  • 2
  • It does seem like 'git rev-parse --git-dir' is the cleanest solution. – Stabledog Aug 09 '13 at 18:10
  • 3
    This fails, when `$GIT_DIR` is detached from the workingtree using `.git`-Files and `gitdir: SOMEPATH`. Consequently this fails for submodules, too, where `$GIT_DIR` contains `.git/modules/SUBMODULEPATH`. – Tino Dec 01 '13 at 08:56
4

Pre-Configured Shell Aliases in Shell Frameworks

If you use a shell framework, there might already be a shell alias available:

  • $ grt in oh-my-zsh (68k) (cd $(git rev-parse --show-toplevel || echo "."))
  • $ git-root in prezto (8.8k) (displays the path to the working tree root)
  • $ g.. zimfw (1k) (changes the current directory to the top level of the working tree.)
Hotschke
  • 9,402
  • 6
  • 46
  • 53
3

I wanted to expand upon Daniel Brockman's excellent comment.

Defining git config --global alias.exec '!exec ' allows you to do things like git exec make because, as man git-config states:

If the alias expansion is prefixed with an exclamation point, it will be treated as a shell command. [...] Note that shell commands will be executed from the top-level directory of a repository, which may not necessarily be the current directory.

It's also handy to know that $GIT_PREFIX will be the path to the current directory relative to the top-level directory of a repository. But, knowing it is only half the battle™. Shell variable expansion makes it rather hard to use. So I suggest using bash -c like so:

git exec bash -c 'ls -l $GIT_PREFIX'

other commands include:

git exec pwd
git exec make
Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
2

In case anyone needs a POSIX compliant way of doing this, without needing git executable:

git-root:

#$1: Path to child directory
git_root_recurse_parent() {
    # Check if cwd is a git root directory
    if [ -d .git/objects -a -d .git/refs -a -f .git/HEAD ] ; then
        pwd
        return 0
    fi

    # Check if recursion should end (typically if cwd is /)
    if [ "${1}" = "$(pwd)" ] ; then
        return 1
    fi

    # Check parent directory in the same way
    local cwd=$(pwd)
    cd ..
    git_root_recurse_parent "${cwd}"
}

git_root_recurse_parent

If you just want the functionality as part of a script, remove the shebang, and replace the last git_root_recurse_parent line with:

git_root() {
    (git_root_recurse_parent)
}
swalog
  • 4,403
  • 3
  • 32
  • 60
  • Caveat: If this is NOT called in a git repo then the recursion does not end at all. – A.H. Feb 11 '19 at 23:07
  • @A.H. Second `if` statement was supposed to check whether you've changed directory in the recursion. If it remains in the same directory, it assumes you're stuck somewhere (e.g. `/`), and brakes recursion. The bug is fixed, and should now work as expected. Thanks for pointing it out. – swalog Feb 12 '19 at 15:27
1

When in a Git worktree, --git-dir gets the path of the worktree metadata inside the .git folder. To get the path of the actual .git folder where hooks can be installed, use --git-common-dir instead:

/Projects/mywork$ git rev-parse --git-dir
/Projects/app/.git/worktrees/mywork
/Projects/mywork$ git rev-parse --git-common-dir
/Projects/app/.git

EDIT: For hooks, there is a better solution:

/Projects/mywork$ git rev-parse --git-path hooks
/Projects/app/.git/hooks
Daniel T
  • 827
  • 9
  • 14
-2

Had to solve this myself today. Solved it in C# as I needed it for a program, but I guess it can be esily rewritten. Consider this Public Domain.

public static string GetGitRoot (string file_path) {

    file_path = System.IO.Path.GetDirectoryName (file_path);

    while (file_path != null) {

        if (Directory.Exists (System.IO.Path.Combine (file_path, ".git")))
            return file_path;

        file_path = Directory.GetParent (file_path).FullName;

    }

    return null;

}
Hylke Bons
  • 133
  • 1
  • 5