有没有办法在一个命令中获取 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='cdgit 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_DIR
s,其名称不是.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 submodule
s 中失败,其中$GIT_DIR
包含类似/.git/modules/SUBMODULE
的内容。您还假设,.git
目录是非裸机情况下工作树的一部分。【参考方案17】:
$ 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"
为什么要投反对票?请突出显示任何错误或提出改进建议。
对我来说 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 中,有没有办法探索当前位置的目录?