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


669

Mercurial可以通过以下方式打印根目录(包含.hg)

hg root

git中是否有等效的东西来获取包含.git目录的目录?


好脚本埃米尔。我使脚本可以在线使用,并允许添加文件/目录作为参数。github.com/Dieterbe/git-scripts/commit/...
Dieter_be

同样,对于bzr root集市中好奇或搜寻的人也非常有用
Kristopher Ives

请注意“ gcd”:相对于具有自动完成功能的存储库根目录的Git感知“ cd”,位于jeetworks.org/node/52
Micha Wiedenmann


1
@MichaWiedenmann:链接已死。
d33tah 2015年

Answers:


1111

是:

git rev-parse --show-toplevel

如果您想直接复制Git命令,可以创建一个别名

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

现在git root将像一样运行hg root


:在一个子模块,这将显示的根目录下的子模块,并没有父库。如果您使用的是Git> = 2.13或更高版本,则子模块可以通过一种方式显示超级项目的根目录。如果您的git早于该版本,请参阅其他答案。


148
我总是定义,git config --global alias.exec '!exec '以便我可以做类似的事情git exec make。之所以可行,是因为外壳别名始终在顶级目录中执行。
Daniel Brockman

8
这是做什么的hg root。它打印出已检出存储库的顶级目录。它不会将您切换到它(事实上,由于当前目录的整个概念和您的shell会如何交互,所以它无法这样做)。
2012年

14
此解决方案对Smal提出了警告-它将跟踪所有符号链接。因此,如果您在~/my.proj/foo/bar~/my.proj与之链接~/src/my.proj,则上述命令会将您移至~/src/my.proj。如果之后要执行的操作与树无关,则可能会出现问题。
弗朗西·佩诺夫(Francis Penov)

3
如何在git hook中使用它?具体来说,我正在执行一个合并后挂接,我需要获取本地git repo的实际根目录。
Derek

12
此解决方案(以及我尝试过的此页面上的许多其他解决方案)在git hook中不起作用。更确切地说,当您位于项目.git目录中时,它不起作用。
丹尼斯2014年

97

(在Alias下)的man页面显示:git-config

如果别名扩展名带有感叹号作为前缀,它将被视为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函数或脚本中。
康拉德·迈耶

4
@cormacrelf将其放在单引号而不是双引号中,那么它将不会在定义时扩展,而是在运行时扩展。
clacke 2013年

3
@Mechanicalsnail真的吗?那么,您希望它在外部回购中做些什么呢?
Alois Mahdal

1
@AloisMahdal实际上对我来说在回购之外不会失败,它只是报告cwd。
justinpitts 2014年

90

--show-toplevel最近才加入git rev-parse或为什么没人提呢?

git rev-parse手册页:

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

1
感谢您指出了这一点; 没有您的帮助,我很难猜到git-rev-parse-因为它的名字表明它与处理修订规范有关。顺便说一句,我很高兴看到git --work-tree类似的工作git --exec-path[=<path>]:“如果没有给出路径,git将显示当前设置”;至少,IMO,寻找这样的功能将是一个合乎逻辑的地方。
imz –伊万·扎哈拉里舍夫(Ivan Zakharyaschev)2012年

4
特别是,您可以root = rev-parse --show-toplevel在gitconfig中使用别名。
机械蜗牛

2
换句话说,您可以做git config --global alias.root "rev-parse --show-toplevel",然后git root将能够完成工作
极性2014年

@RyanTheLeach git rev-parse --show-toplevel在子模块中尝试时起作用。它输出git子模块的根目录。它为您打印什么?
wisbucky

2
我很困惑为什么这个答案重复了最重要的答案。原来,最高答案的编辑时间--show-cdup--show-top-level2011年2月至(在提交此答案之后)。
wisbucky

54

怎么样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手册页

--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+(Q1 2020)一起使用


3
哦,等等,这很接近,但是它获取了实际的.git目录,而不是git repo的基础。另外,.git目录可能在其他位置,所以这不是我想要的。
wojo

2
是的,我现在看到您真正要寻找的东西了。--show-cdup则更合适。我留下我的答案来说明两个选项之间的区别。
VonC

2
.git如果您已经在根目录中,该命令似乎也会提供一个相对路径。(至少它对msysgit有用。)
Blair Holloway 2010年

2
+1,这是回答原始问题“获取包含.git目录的目录吗?”的唯一答案,很有趣地看到OP本身提到“ .git目录可能在其他位置”。
FabienAndre

1
这是在Windows上运行的唯一解决方案,并且在使用git子模块(例如c:\ repositories \ myrepo \ .git \ modules \ mysubmodule)时会给出可分析的结果。这使其成为最可靠的解决方案,尤其是在可重用脚本中。
chriskelly 2014年

36

在这里写一个简单的答案,以便我们可以使用

git root

要完成这项工作,只需使用以下命令配置您的git

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

然后您可能需要将以下内容添加到您的~/.bashrc

alias cdroot='cd $(git root)'

这样您就可以使用cdroot转到回购的顶部。


非常好!最终方便!
scravy

一个很好的答案,谢谢!
AVarf

26

如果您已经在顶层或不在git存储库中,cd $(git rev-parse --show-cdup)则会带您回家(仅使用cd)。 cd ./$(git rev-parse --show-cdup)是解决该问题的一种方法。


7
另一种选择是引用:cd "$(git rev-parse --show-cdup)"。之所以有效,是因为cd ""它使您无所事事,而不是返回$HOME。最好还是引用$()调用,以防它们输出带有空格的东西(不过,在这种情况下,此命令不会)。
2015年

即使通过符号链接将目录更改为git repo 此答案也可以正常使用。当您的git repo在符号链接下时,其他一些示例将无法正常工作,因为它们无法解析git相对于bash的根目录$PWD。这个例子混帐的根相将解析为$PWD代替realpath $PWD
DamienÓCeallaigh 2015年

16

要计算当前git根目录的绝对路径(例如要在shell脚本中使用),请使用readlink和git rev-parse的这种组合:

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

git-rev-parse --show-cdup给您正确的数字“ ..”以从您的cwd到达根,如果您位于根,则为空字符串。然后在前缀“ ./”之前处理空字符串,并用于 readlink -f转换为完整路径。

您还可以git-root在PATH中创建一个命令作为外壳程序脚本来应用此技术:

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脚本中,如果rev-parse失败,请使用“ -e”导致shell退出,这样,如果您不在git目录中,则可以正确获取退出状态和错误消息。


2
如果git“ root”目录路径包含空格,则示例将中断。始终使用"$(git rev-parse ...)"代替hack之类的东西./$(git rev-parse ...)
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 rev-parse --show-cdup。但是,有一些边缘情况需要解决:

  1. 当cwd已经是工作树的根时,该命令将产生一个空字符串。
    实际上,它会产生一个空行,但命令替换会删除尾随的换行符。最终结果是一个空字符串。

    大多数的答案建议前面加上输出,./使空的输出成为"./"它被送到之前cd

  2. 当GIT_WORK_TREE设置为不是cwd父级的位置时,输出可能是绝对路径名。

    ./在这种情况下,前置是错误的。如果将a ./放在绝对路径之前,则它将成为相对路径(如果cwd是系统的根目录,则它们仅引用相同的位置)。

  3. 输出可能包含空格。

    这实际上仅适用于第二种情况,但是它有一个简单的解决方法:在命令替换(以及该值的任何后续用法)周围使用双引号。

正如其他答案所指出的,我们可以这样做cd "./$(git rev-parse --show-cdup)",但是这会在第二种情况(如果我们不使用双引号的情况下在第三种情况)中中断。

许多shell都将其cd ""视为无操作对象,因此我们可以做到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'?
杰森·阿克森

1
可能无法使用别名,例如,如果要编写脚本脚本并且不能依赖自定义别名。
2013年

1
子模块是另一个极端情况。
瑞恩(Ryan)泄漏

14

以防万一,如果您要将此路径传递到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)

12

与子模块,挂钩和.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) ))

要在子模块根目录中轻松执行命令,请在[alias].gitconfig添加:

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"
}

通过键入执行它git_root(重新启动后壳:exec bash


该代码正在Robust bash函数处进行代码审查,以查找git信息库的根。在那寻找更新。
汤姆·黑尔

真好 比我7年前提出的建议更复杂(stackoverflow.com/a/958125/6309),但仍然+1
VonC

1
遵循这些原则的较短解决方案是: (root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd)但这不包括外部名称$GIT_DIR,这些名称不是.git
汤姆·黑尔

我不知道这是否考虑到多个worktrees是现在可以在git的2.5+(stackoverflow.com/a/30185564/6309
VonC

您的注释链接到“ Robust bash函数以查找根...”现在给出
404。– ErikE

8

要修改“ git config”答案,请执行以下操作:

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

并清理路径。非常好。


7

如果您正在寻找一个好的别名来执行此操作,cd并且如果您不在git目录中,也不要感到厌烦:

alias ..g='git rev-parse && cd "$(git rev-parse --show-cdup)"'


6

无论您是在git子目录中还是在顶层,此shell别名都适用:

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

更新为使用现代语法而不是反引号:

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

5
alias git-root='cd \`git rev-parse --git-dir\`; cd ..'

其他所有操作都在某个时候失败,要么进入主目录,要么不幸失败。这是回到GIT_DIR的最快和最短的方法。


看来'git rev-parse --git-dir'是最干净的解决方案。
2013年

3
$GIT_DIR使用-Files .git和从工作树中分离时,此操作将失败gitdir: SOMEPATH。因此,这对于$GIT_DIR包含的子模块也失败.git/modules/SUBMODULEPATH
Tino 2013年

5

这是我编写的可处理这两种情况的脚本:1)具有工作区的存储库,2)裸存储库。

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

git-root (您路径中的可执行文件):

#!/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
)

希望这会有所帮助。


1
Keith,谢谢您的建议,我已经包含了脚本。
jdsumsion

我实际上赞成最高答案,因为我意识到这个git exec想法在非裸仓库中更有用。但是,我的答案中的此脚本可以正确处理裸机与非裸机情况,这可能对某人有用,因此我将其留在此处。
jdsumsion

1
但是,这在内包含的内容内git submodule的失败。您还假设,在非裸露的情况下,该目录是工作树的一部分。$GIT_DIR/.git/modules/SUBMODULE.git
蒂诺,2013年

4
$ git config alias.root '!pwd'
# then you have:
$ git root

该别名将作为全局别名失败。改用它(在〜/ .gitconfig中):[alias] findroot = "!f () { [[ -d ".git" ]] && echo "Found git in [pwd ]" && exit 0; cd .. && echo "IN pwd" && f;}; f"
FractalSpace 2013年

为什么要投票?请突出显示任何错误或建议改进。
FractalSpace

1
对我而言git config --global alias.root '!pwd'。我无法发现任何情况都与非全局变量有所不同。(Unix,git 1.7.10.4)BTW:您findroot需要a /.git来避免无限递归。
Tino 2013年

4

Git 2.13.0开始,它支持一个新选项来显示根项目的路径,即使从子模块内部使用它也可以正常工作:

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

git-scm.com/docs/…。有趣。我一定错过了那个。+1
VonC '18年

3
但是警告:“如果任何项目未将当前存储库用作子模块,则不输出任何内容。”
VonC

感谢您的评论!我没有注意到。
Jordi Vilalta Prat

2

Shell框架中的预配置Shell别名

如果使用外壳程序框架,则可能已经有可用的外壳程序别名:


1

我想补充一下丹尼尔·布罗克曼的出色评论。

定义git config --global alias.exec '!exec '允许您执行以下操作:git exec make因为man git-config状态如下:

如果别名扩展名带有感叹号作为前缀,它将被视为shell命令。[...]注意,shell命令将从存储库的顶级目录执行,该目录不一定是当前目录。

知道$GIT_PREFIX相对于存储库顶级目录的当前目录的路径也很方便。但是,知道这只是成功的一半。Shell变量扩展使其很难使用。所以我建议这样使用bash -c

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

其他命令包括:

git exec pwd
git exec make

1

如果任何人都需要执行POSIX兼容方式而不需要git可执行文件:

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

如果只希望将功能作为脚本的一部分,请删除shebang,然后将最后git_root_recurse_parent一行替换为:

git_root() {
    (git_root_recurse_parent)
}

注意:如果未在git repo中调用此函数,则递归根本不会结束。
AH

@AH第二条if语句应该检查您是否在递归中更改了目录。如果它位于同一目录中,则假定您被卡在某个位置(例如/),并阻止递归。该错误已修复,现在应该可以按预期运行。感谢您指出。
swalog

0

今天不得不自己解决这个问题。我在程序中需要时用C#解决了它,但是我想它可以轻松地重写。考虑这个公共领域。

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;

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.