有没有一种方法可以用一个命令获取git根目录?

972

Mercurial提供了一种通过以下方式打印根目录(包含.hg文件夹)的方法:

hg root

在Git中是否有类似的功能可以获取包含.git目录的目录?


很不错的脚本,Emil。 我已经将脚本发布到了网上,并允许添加文件/目录作为参数。 http://github.com/Dieterbe/git-scripts/commit/3b7be5e25052e99d9be8ca7d9e6d5c91f8c2a14a - Dieter_be
对于任何好奇或搜索“bzr root”的人来说,它在Bazaar中被广泛使用。 - Kristopher Ives
请注意 'gcd' : Git-Aware 'cd' Relative to Repository Root with Auto-Completion,网址为 http://jeetworks.org/node/52。 - Micha Wiedenmann
3
请参考我在这个评论中解释的我的git rev-parse --git-dir答案 - VonC
1
@MichaWiedenmann:链接已失效。 - d33tah
1
@d33tah 存档链接 https://web.archive.org/web/20130526035714/http://jeetworks.org/node/52 - adam_0
24个回答

1619
是的:
git rev-parse --show-toplevel

如果你想更直接地复制Mercurial命令,你可以创建一个别名
git config --global alias.root 'rev-parse --show-toplevel'

现在,git root将像hg root一样正常工作。
注意:在子模块中,这将显示子模块的根目录,而不是父存储库。如果您使用的是Git >=2.13或更高版本,则有一种方法可以显示超级项目的根目录。如果您的git版本较旧,请参阅另一个答案。

192
我通常会定义git config --global alias.exec '!exec ',这样我就可以执行类似于git exec make的操作。这是因为Shell别名始终在顶层目录中执行。 - Daniel Brockman
8
这就是hg root的作用。它会打印出你检出仓库的顶级目录,但不会切换到该目录(实际上也无法这样做,因为当前目录和你的 shell 交互的整个概念的限制)。 - Omnifarious
15
这个解决方案有一个小提示 - 它会跟随任何符号链接。所以,如果你在~/my.proj/foo/bar目录下,并且~/my.proj是一个符号链接,指向~/src/my.proj,那么上面的命令将把你移动到~/src/my.proj。如果之后需要对目录树结构敏感,这可能会成为一个问题。 - Franci Penov
3
我如何在git hook中使用它?具体来说,我正在执行post-merge hook,并且需要获取本地git repo的实际根目录。 - Derek
17
这个解决方案(以及我尝试过的此页面上的许多其他方案)无法在 git hook 中运行。更准确地说,在你进入项目的.git目录时,它不起作用。 - Dennis
显示剩余19条评论

115

--show-toplevel 是最近才添加到 git rev-parse 中的吗?为什么没有人提到它?

来自 git rev-parse 的 man 页面:

   --show-toplevel
       Show the absolute path of the top-level directory.

1
感谢您指出这一点;没有您的帮助,我很难猜测要查找 git-rev-parse -- 因为它的名称暗示它是关于处理修订规范的。顺便说一句,我很高兴看到 git --work-tree 的工作方式类似于 git --exec-path[=<path>]:"如果没有给出路径,git 将打印当前设置";至少,在我看来,这将是寻找此类功能的一个逻辑位置。 - imz -- Ivan Zakharyaschev
4
特别是,您可以在gitconfig中将root = rev-parse --show-toplevel设置为别名。 - Mechanical snail
2
你可以执行 git config --global alias.root "rev-parse --show-toplevel",然后 git root 就能完成这个任务。 - nonopolarity
@RyanTheLeach,我在子模块中尝试使用 git rev-parse --show-toplevel 命令,成功获取了 Git 子模块的根目录。请问你执行该命令后输出的是什么? - wisbucky
5
我感到困惑为什么这个答案与排名第一的答案重复了。原来,排名第一的答案在2011年2月份被编辑,将参数从“--show-cdup”改为“--show-top-level”(在此答案提交之后)。 - wisbucky
显示剩余2条评论

108
git-configman页(在别名下)说道:

如果别名扩展以感叹号开头,它将被视为一个shell命令。[...] 请注意,shell命令将从存储库的顶级目录执行,这可能不一定是当前目录。

所以,在UNIX上你可以这样做:
git config --global --add alias.root '!pwd'

2
如果你有一个“git root”命令,你怎么把它放到别名里?如果我把它放在我的.zshrc文件中,并定义alias cg="cd $(git root)",那么$()部分会在源代码时间进行评估,并且始终指向~/dotfiles,因为那是我的zshrc所在的位置。 - zelk
2
@cormacrelf 你不应该将它放在 shell 别名中。你可以将它放在 shell 函数或脚本中。 - Conrad Meyer
5
将其用单引号而不是双引号括起来,这样它就不会在定义时被展开,而是在运行时被展开。 - clacke
3
@Mechanicalsnail 真的吗?那么你希望它在存储库之外做什么? - Alois Mahdal
1
@AloisMahdal 对我来说,它在存储库外并不失败,它只是报告当前工作目录。 - justinpitts
显示剩余7条评论

98

试试使用“git rev-parse --git-dir”命令?

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

--git-dir 选项似乎有效。

即使在一个裸库中也可以正常工作,而 git rev-parse --show-toplevel会触发(在该裸库中)一个"fatal: this operation must be run in a work tree" 的错误信息。

参见 git rev-parse 手册页面

--git-dir

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

你可以在这个git setup-sh脚本中看到它的运行。

如果你在一个子模块文件夹中,使用Git>=2.13,请使用:

git rev-parse --show-superproject-working-tree
如果你正在使用 git rev-parse --show-toplevel请确保使用的是 Git 2.25+ (2020年第一季度)

4
等一下,这个很接近但是它得到的是实际的 .git 目录,而不是 git 仓库的基础目录。此外,.git 目录可能在其他地方,所以这并不完全是我要找的内容。 - wojo
2
好的,我现在明白您真正需要什么了。--show-cdup 更加适合。我保留我的回答来说明两个选项之间的区别。 - VonC
2
如果您已经在根目录中,该命令似乎为.git提供了一个相对路径。(至少,在msysgit上是这样。) - Blair Holloway
3
+1,这是唯一回答最初问题“获取包含.git目录的目录”的答案,有趣的是OP本身提到“.git目录可能在其他地方”。 - FabienAndre
3
谢谢。我实际上想要 Git 目录,并且这也适用于子模块,它们的顶层文件夹下不一定有 .git/ 文件夹。 - joeytwiddle
显示剩余5条评论

52
写一个简单的答案在这里,这样我们就可以使用。
git root

要完成这项工作,只需使用以下步骤配置您的git:
git config --global alias.root "rev-parse --show-toplevel"

然后你可能想要将以下内容添加到你的~/.bashrc中:
alias cdroot='cd $(git root)'

这样你就可以直接使用cdroot命令进入你的代码库的顶层。

非常好!极其方便! - scravy

28
如果你已经在顶级目录或不在 git 仓库中,cd $(git rev-parse --show-cdup)会将你带回到主目录(即执行 cd 命令)。cd ./$(git rev-parse --show-cdup)是解决这个问题的一种方式。

10
另一种选择是引用它:cd "$(git rev-parse --show-cdup)"。这种方式有效,因为 cd "" 不会返回 $HOME,而是不会有任何反应。无论如何,最佳实践是引用 $() 调用,以防其输出包含空格(尽管在此情况下此命令不会输出空格)。 - ctrueden
1
即使您通过符号链接更改了目录到您的git repo(通过此链接),此答案仍然有效。当您的git repo位于符号链接下时,其他一些示例无法按预期工作,因为它们不会将git的根目录解析为相对于bash的$PWD。此示例将解析git的根目录相对于$PWD而不是realpath $PWD - Damien

21

与子模块、钩子和 .git 目录内部的简短解决方案

这里是大多数人想要的简短答案:

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

这将在git工作树的任何位置(包括.git目录内部)起作用,但是假设存储库目录被称为.git(这是默认值)。对于子模块,这将转到最外层包含存储库的根目录。

如果您想要进入当前子模块的根目录,请使用:

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

为了在子模块根目录下轻松执行命令,在你的.gitconfig中的[alias]下添加:

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

这使得你可以轻松地完成像git sh ag <string>这样的操作。

强大的解决方案,支持命名不同或外部.git$GIT_DIR目录。

请注意,$GIT_DIR可能指向外部某个位置(并且不被称为.git),因此需要进一步检查。

将此放入您的.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"
}

在重新启动您的shell后,通过输入git_root来执行它(使用以下命令重新启动: exec bash


此代码正在进行代码审查,位于强大的bash函数以查找git存储库的根目录。请在那里查看更新。 - Tom Hale
不错。比我7年前提出的(https://dev59.com/cHNA5IYBdhLWcg3wcddk#958125)更复杂,但仍然给个赞。 - VonC
1
类似的较短解决方案是:(root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd),但这并不包括命名为.git以外的外部$GIT_DIR - Tom Hale
我想知道这是否考虑了在 git 2.5+ 中现在可能存在的多个工作树(https://dev59.com/SW025IYBdhLWcg3wAg_p#30185564)。 - VonC
您的评论链接“Robust bash function to find the root ...”现在已经失效,无法访问。 - ErikE
显示剩余4条评论

18
正如其他人所指出的,解决方案的核心是使用git rev-parse --show-cdup命令。然而,还有一些特殊情况需要处理:
  1. 当当前工作目录已经是工作树的根目录时,该命令返回一个空字符串。
    实际上,它会生成一个空行,但命令替换会去掉尾随的换行符。最终的结果是一个空字符串。
Most answers suggest prepending the output with `./` so that an empty output becomes `"./"` before it is fed to `cd`.

当GIT_WORK_TREE设置为不是当前工作目录的父目录时,输出可能是绝对路径名。
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).

输出可能包含空格。
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).

如其他答案所指出,我们可以执行cd "./$(git rev-parse --show-cdup)",但这在第二个边缘情况下会出错(如果我们省略双引号,则在第三个边缘情况下也会出错)。
许多shell将cd ""视为无操作,因此对于这些shell,我们可以执行cd "$(git rev-parse --show-cdup)"(双引号保护空字符串作为第一个边缘情况的参数,并保留第三个边缘情况中的空格)。POSIX规定cd ""的结果是未指定的,因此最好避免做出这种假设。
在所有上述情况下都能正常工作的解决方案需要进行某种形式的测试。显式地完成,可能如下所示:
cdup="$(git rev-parse --show-cdup)" && test -n "$cdup" && cd "$cdup"

第一个边缘情况不需要执行cd
如果在第一个边缘情况中可以运行cd .,那么条件可以在参数的扩展中完成。
cdup="$(git rev-parse --show-cdup)" && cd "${cdup:-.}"

为什么不直接使用“git config --global --add alias.root '!pwd'”和一个shell别名gitroot='cd git root',就像你上面的答案所使用的那样? - Jason Axelson
1
使用别名可能不可行,例如,如果您想要编写脚本并且不能依赖自定义别名。 - blueyed
1
子模块是另一个特殊情况。 - Ryan Leach

17

为了在shell脚本中计算当前git根目录的绝对路径,请使用readlink和git rev-parse的组合:

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

git-rev-parse --show-cdup命令会给出从当前工作目录到根目录的正确数量的"..", 如果你已经在根目录,则会返回空字符串。 然后在空字符串情况下添加"./", 并使用readlink -f将其转换为完整路径。

你也可以在PATH中创建一个名为git-root的命令,它是一个shell脚本,使用这种技术:

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

(上述内容可粘贴到终端中创建git-root并设置执行位;实际脚本在第2、3和4行)

然后您就可以运行git root来获取当前树的根目录。请注意,在shell脚本中,使用“-e”使shell在rev-parse失败时退出,以便您可以正确获取退出状态和错误消息(如果您不在git目录中)。


2
如果git的“根”目录路径包含空格,则您的示例将会出错。请始终使用“$(git rev-parse ...)”而不是像“./$(git rev-parse ...)”这样的hack方法。 - Mikko Rantalainen
readlink -f在BSD上的工作方式不同。请参见此SO以获取解决方法。Python答案可能无需安装任何内容即可正常工作:python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$(git rev-parse --show-cdup)" - Bluu

16
如果你将这个路径传递给Git本身,请使用:/
# 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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接