有没有办法在一个命令中获取 git 根目录?

Posted

技术标签:

【中文标题】有没有办法在一个命令中获取 git 根目录?【英文标题】:Is there a way to get the git root directory in one command? 【发布时间】:2010-10-31 18:30:41 【问题描述】:

Mercurial 有一种打印根目录(包含 .hg)的方法

hg root

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

【问题讨论】:

好剧本埃米尔。我使脚本在线可用,并允许添加文件/目录作为参数。 github.com/Dieterbe/git-scripts/commit/… 对于任何好奇或搜索的人,bzr root 在 Bazaar 中被大量使用 考虑my answer below git rev-parse --git-dir,正如in this comment所解释的那样 @MichaWiedenmann:链接已失效。 @d33tah 存档链接web.archive.org/web/20130526035714/http://jeetworks.org/node/52 【参考方案1】:

是的:

git rev-parse --show-toplevel

如果您想更直接地复制 Mercurial 命令,可以创建一个alias:

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

现在git root 的功能与hg root 一样。


注意:在子模块中,这将显示子模块的根目录,而不是父存储库。如果您使用的是 Git >=2.13 或更高版本,有一种方法是 submodules can show the superproject's root directory. 如果您的 git 比这更旧,see this other answer.

【讨论】:

我总是定义git config --global alias.exec '!exec ',所以我可以做git exec make之类的事情。这是因为 shell 别名总是在***目录中执行。 这就是hg root 所做的。它打印出您签出的存储库的***目录。它不会让你切换到它(事实上,它不能这样做,因为当前目录的整个概念和你的 shell 是如何交互的)。 这个解决方案的小警告 - 它会跟进任何符号链接。因此,如果您在~/my.proj/foo/bar 中,并且~/my.proj 符号链接到~/src/my.proj,则上述命令会将您移动到~/src/my.proj。可能是一个问题,如果你想在这之后做的不是树不可知论。 如何在 git 挂钩中使用它?具体来说,我正在做一个 post-merge 钩子,我需要获取本地 git repo 的实际根目录。 此解决方案(以及我尝试过的此页面上的许多其他解决方案)在 git 挂钩中不起作用。更准确地说,当您在项目的 .git 目录中时,它不起作用。【参考方案2】:

--show-toplevel 是最近才被添加到 git rev-parse 还是为什么没有人提及它?

来自git rev-parse 手册页:

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

【讨论】:

感谢您指出这一点;没有你的帮助,我很难猜到git-rev-parse——因为它的名字暗示它是关于处理修订规范的。顺便说一句,我很高兴看到git --work-tree 的工作类似于git --exec-path[=<path>]:“如果没有给出路径,git 将打印当前设置”;至少,IMO,寻找这样的功能是一个合乎逻辑的地方。 特别是,你可以在你的 gitconfig 中给 root = rev-parse --show-toplevel 取别名。 也就是说,你可以做git config --global alias.root "rev-parse --show-toplevel",然后git root就能做这个工作 @RyanTheLeach git rev-parse --show-toplevel 在我在子模块中尝试时有效。它打印 git 子模块的根目录。它为您打印什么? 我很困惑为什么这个答案重复了最佳答案。事实证明,2011 年 2 月(在提交此答案之后)将最佳答案从 --show-cdup 编辑为 --show-top-level【参考方案3】:

git-config(在别名下)的man页面说:

如果别名扩展以感叹号为前缀,将被视为shell 命令。 [...] 请注意,外壳 命令将从存储库的***目录执行,这可能不会 必须是当前目录。

所以,在 UNIX 上你可以这样做:

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

【讨论】:

那么,如果你有一个“git root”命令,你怎么能把它放在别名中呢?如果我把它放在我的.zshrc 中,并且我定义了 `alias cg="cd $(git root)",$() 部分会在源代码时被评估,并且总是指向 ~/dotfiles,因为那是我的 zshrc 是。 @cormacrelf 你不要把它放在 shell 别名中。你可以把它放在一个shell函数或脚本中。 @cormacrelf 用单引号代替双引号,这样在定义时就不会展开,而是在运行时展开。 @Mechanicalsnail 真的吗?那么你希望它在外部回购做什么? @AloisMahdal 实际上对我来说它不会在回购之外失败,它只是报告 cwd。【参考方案4】:

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 manual page:

--git-dir

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

您可以在git setup-sh script 中看到它的实际效果。

如果您在子模块文件夹中,with Git >=2.13, use:

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

如果您使用的是git rev-parse --show-toplevel,make sure it is with Git 2.25+ (Q1 2020)。

【讨论】:

哦,等等,这很接近,但它获得了实际的 .git 目录,而不是 git repo 的基础。此外,.git 目录可能在其他地方,所以这不是我想要的。 对,我现在知道你真正在寻找什么了。 --show-cdup 更合适。我留下我的答案来说明这两个选项之间的区别。 如果您已经在根目录中,该命令似乎也为.git 提供了相对路径。 (至少,它在 msysgit 上是这样。) +1,这是回答原始问题“获取包含 .git 目录的目录?”的唯一答案,有趣的是看到 OP 本身提到“.git 目录可能在其他地方"。 谢谢。我实际上想要 git 目录,这也适用于子模块,它们的***文件夹下不一定有 .git/ 文件夹。【参考方案5】:

这里写一个简单的答案,方便大家使用

git root

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

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

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

alias cdroot='cd $(git root)'

这样您就可以使用 cdroot 转到您的存储库的顶部。

【讨论】:

非常好!非常方便!【参考方案6】:

如果您已经在***或不在 git 存储库中,cd $(git rev-parse --show-cdup) 将带您回家(只需 cd)。 cd ./$(git rev-parse --show-cdup) 是解决此问题的一种方法。

【讨论】:

另一种选择是引用它:cd "$(git rev-parse --show-cdup)"。这是因为cd "" 不会带你去任何地方,而不是返回$HOME。最好还是引用 $() 调用,以防它们输出带有空格的内容(但在这种情况下,此命令不会)。 即使您将目录更改为 git repo via a symlink,此答案也很有效。当您的 git 存储库位于符号链接下时,其他一些示例无法按预期工作,因为它们没有解析相对于 bash 的 $PWD 的 git 根目录。此示例将解析 git 的根相对于 $PWD 而不是 realpath $PWD【参考方案7】:

正如其他人所指出的,解决方案的核心是使用git rev-parse --show-cdup。但是,有一些边缘情况需要解决:

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

    大多数答案建议在输出前加上./,以便空输出在输入到cd 之前变为"./"

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

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

    输出可能包含空格。

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

正如其他答案所指出的那样,我们可以使用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' ? 可能无法使用别名,例如如果您想编写脚本并且不能依赖自定义别名。 子模块是另一种极端情况。【参考方案8】:

适用于子模块、挂钩和.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"

输入git_root 执行它(重启你的shell后:exec bash

【讨论】:

此代码正在Robust bash function to find the root of a git repository 进行代码审查。在那里寻找更新。 不错。比我 7 年前提出的 (***.com/a/958125/6309) 更复杂,但仍然 +1 这些方面的较短解决方案是:(root=$(git rev-parse --git-dir)/ && cd $root%%/.git/* && git rev-parse && pwd),但这不包括外部$GIT_DIRs,其名称不是.git 我想知道这是否考虑到了 git 2.5+ (***.com/a/30185564/6309) 中现在可能的多个工作树 您的评论链接到“Robust bash function to find the root ...”现在给出了 404。【参考方案9】:

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

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

git-rev-parse --show-cdup 为您提供正确数量的“..”以获取 从您的 cwd 到根目录,或者如果您在根目录,则为空字符串。 然后在前面加上“./”来处理空字符串大小写并使用 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脚本中,如果rev-parse失败,请使用“-e”使shell退出,这样如果您不在git目录中,您可以正确获取退出状态和错误消息。

【讨论】:

如果 git "root" 目录路径包含空格,您的示例将中断。始终使用"$(git rev-parse ...)" 而不是像./$(git rev-parse ...) 这样的黑客。 readlink -f 在 BSD 上的工作方式不同。有关解决方法,请参阅this SO。 Python 答案可能无需安装任何东西就可以工作:python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$(git rev-parse --show-cdup)".【参考方案10】:

以防万一您将此路径提供给 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)

【讨论】:

【参考方案11】:

稍微修改一下“git config”的答案:

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

并清理路径。很不错。

【讨论】:

【参考方案12】:

如果您正在寻找一个好的别名来执行此操作,并且如果您不在 git 目录中,请不要炸毁 cd

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

【讨论】:

【参考方案13】:

git-extras

添加$ git root 见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

git-extras 的可用性

homebrew(osx)/linuxbrew(linux)$ brew install git-extras debian/ubuntu repos (https://packages.debian.org/sid/git-extras) $ apt-get install git-extras

【讨论】:

【参考方案14】:

无论您是在 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)'

【讨论】:

【参考方案15】:
alias git-root='cd \`git rev-parse --git-dir\`; cd ..'

其他一切都在某个时候失败,要么进入主目录,要么惨遭失败。这是返回 GIT_DIR 的最快、最短的方法。

【讨论】:

看起来 'git rev-parse --git-dir' 是最干净的解决方案。 $GIT_DIR 使用.git-Files 和gitdir: SOMEPATH 与工作树分离时,此操作将失败。因此,这对于子模块也会失败,其中$GIT_DIR 包含.git/modules/SUBMODULEPATH【参考方案16】:

这是我编写的处理这两种情况的脚本: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
)

希望这会有所帮助。

【讨论】:

Keith,谢谢你的建议,我已经包含了脚本。 我实际上赞成最佳答案,因为我意识到git exec 的想法在非裸存储库中更有帮助。但是,我的答案中的这个脚本正确处理了裸与非裸的情况,这可能对某人有用,所以我把这个答案留在这里。 但是这在git submodules 中失败,其中$GIT_DIR 包含类似/.git/modules/SUBMODULE 的内容。您还假设,.git 目录是非裸机情况下工作树的一部分。【参考方案17】:
$ git config alias.root '!pwd'
# then you have:
$ git root

【讨论】:

此别名将作为全局别名失败。改用它(在 ~/.gitconfig 中):[alias] findroot = "!f () [[ -d ".git" ]] &amp;&amp; echo "Found git in [pwd]" &amp;&amp; exit 0; cd .. &amp;&amp; echo "IN pwd" &amp;&amp; f;; f" 为什么要投反对票?请突出显示任何错误或提出改进建议。 对我来说 git config --global alias.root '!pwd' 有效。我无法发现它与非全局变体不同的任何情况。 (Unix,git 1.7.10.4)顺便说一句:您的findroot 需要/.git 以避免无休止的递归。【参考方案18】:

从Git 2.13.0开始,它支持显示根项目路径的新选项,即使在子模块内部使用也可以:

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

【讨论】:

git-scm.com/docs/…。有趣的。我一定错过了那个。 +1 警告:“如果当前存储库未被任何项目用作子模块,则不输出任何内容。” 感谢您的评论!我没有注意到。【参考方案19】:

Shell 框架中预配置的 Shell 别名

如果您使用 shell 框架,可能已经有可用的 shell 别名:

$ grt in oh-my-zsh (68k) (cd $(git rev-parse --show-toplevel || echo ".")) $ git-root in prezto (8.8k)(显示工作树根的路径) $ g..zimfw (1k)(将当前目录更改为工作树的顶层。)

【讨论】:

【参考方案20】:

我想扩展 Daniel Brockman 的出色评论。

定义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

【讨论】:

【参考方案21】:

如果有人需要符合 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 中没有调用它,那么递归根本不会结束。 @A.H.第二个if 语句应该检查您是否在递归中更改了目录。如果它保留在同一目录中,则假定您被卡在某个地方(例如/),并停止递归。该错误已修复,现在应该可以按预期工作。感谢您指出。【参考方案22】:

今天必须自己解决这个问题。在 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;


【讨论】:

当工作目录和.git 目录位于不同的路径时,此操作会失败(请参阅有关 GIT_WORK_TREE 和 GIT_DIR 的其他答案/cmets)。

以上是关于有没有办法在一个命令中获取 git 根目录?的主要内容,如果未能解决你的问题,请参考以下文章

在 Windows 7 Git Bash 中,有没有办法探索当前位置的目录?

Git status - 有没有办法只在特定目录中显示更改?

如何从 git grep 搜索中排除某些目录/文件

iOS - 获取 Git 仓库(分布式版本控制系统)

git clone复制远程代码覆盖非空目录

2.获取git仓库