关于版本控制:有没有办法在一个命令中获取git根目录?

Is there a way to get the git root directory in one command?

Mercurial可以通过

1
hg root

在git中是否有类似的东西来获取包含.git目录的目录?


对:

1
git rev-parse --show-toplevel

注意:在子模块中,这将显示子模块的根目录,而不是父存储库。如果您使用的是git>=2.13或更高版本,下面是子模块显示超级项目根目录的方法。如果你的Git比这个年龄大,请看另一个答案。


git-configman页面(化名)显示:

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.

因此,在Unix上,您可以执行以下操作:

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


--show-toplevel是不是最近才被添加到git rev-parse中,或者为什么没有人提到?

git rev-parse手册页:

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


"git rev-parse --git-dir"怎么样?

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

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

从Git Rev Parse Manual页面:

1
2
3
--git-dir

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

你可以在这个git setup-sh脚本中看到它的实际作用。

如果您在子模块文件夹中,且git大于等于2.13,请使用:

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


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

1
git root

要完成此任务,只需使用

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

然后,您可能希望在您的~/.bashrc中添加以下内容:

1
alias cdroot='cd $(git root)'

这样你就可以使用cdroot进入回购的顶部。


如果你已经在顶级或不在Git存储库中,cd $(git rev-parse --show-cdup)会带你回家(只有CD)。cd ./$(git rev-parse --show-cdup)是解决这个问题的一种方法。


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

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

    大多数答案都建议在输出前加上./,使空输出在被送入cd之前变成"./"

  • 当Git_工作树设置为不是CWD父级的位置时,输出可能是绝对路径名。

    在这种情况下,预支./是错误的。如果一个./被预先设置为一个绝对路径,它就成为一个相对路径(如果cwd是系统的根目录,它们只引用相同的位置)。

  • 输出可能包含空白。

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

  • 正如其他答案所指出的,我们可以使用cd"./$(git rev-parse --show-cdup)",但这会在第二个边缘案例(如果我们不使用双引号,则会在第三个边缘案例)中中断。

    许多shell将cd""视为no op,因此对于这些shell,我们可以执行cd"$(git rev-parse --show-cdup)"(双引号在第一个边缘大小写中保护空字符串作为参数,在第三个边缘大小写中保留空白)。posix说,cd""的结果没有具体说明,因此最好避免作出这种假设。

    在上述所有情况下都有效的解决方案都需要某种类型的测试。显式完成后,可能如下所示:

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

    对于第一个边缘情况,不执行cd

    如果可以为第一个边缘情况运行cd .,则可以在扩展参数时执行条件:

    1
    cdup="$(git rev-parse --show-cdup)" && cd"${cdup:-.}"


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

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

    git-rev-parse --show-cdup给了你正确的"…"号从CWD的根目录,或者如果在根目录,则为空字符串。然后预加"./"以处理空字符串大小写并使用readlink -f转换为完整路径。

    您还可以在路径中创建一个git-root命令作为shell脚本来应用此技术:

    1
    2
    3
    4
    5
    6
    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根,设置执行位,实际脚本在第2、3、4行)

    然后你就可以运行git root来获取当前树的根。注意,在shell脚本中,如果rev解析失败,则使用"-e"使shell退出,这样,如果您不在git目录中,就可以正确地获取退出状态和错误消息。


    为了防止您将此路径馈送给Git本身,请使用:/

    1
    2
    3
    4
    5
    # 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)


    与子模块、钩子和.git目录内一起工作的简短解决方案

    以下是大多数人想要的简短答案:

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

    这在Git工作树(包括.git目录内)的任何位置都可以工作,但假定存储库目录称为.git(默认情况下)。对于子模块,这将转到包含存储库的最外层的根目录。

    如果要获取当前子模块的根,请使用:

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

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

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

    这样你就可以很容易地做像git sh ag 这样的事情。

    支持不同名称或外部.git$GIT_DIR目录的健壮解决方案。

    请注意,$GIT_DIR可能指向外部的某个地方(不称为.git),因此需要进一步检查。

    把这个放在你的.bashrc里:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    # 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 # git_root will print any errors
      cd"$root"
    }

    通过键入git_root(在重新启动shell:exec bash之后)来执行它。


    要修改"git-config",只需回答一点:

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

    把小路清理干净。很不错的。


    如果你正在寻找一个好的别名来做这件事,如果你不在git目录中,就不要炸毁cd

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

    git-extras

    增加$ git root。请参见https://github.com/tj/git extras/blob/master/commands.md git root

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

    Git Extras的可用性

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


    无论您是在Git子目录中,还是在顶层,此shell别名都有效:

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

    更新后使用现代语法而不是背景符号:

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


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

    其他的事情在某个时候都会失败,要么是转到主目录,要么就是非常糟糕的失败。这是返回Git_目录的最快和最短的方法。


    下面是我编写的一个处理这两种情况的脚本:1)带有工作区的存储库,2)空存储库。

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/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
    2
    3
    $ git config alias.root '!pwd'
    # then you have:
    $ git root


    自从Git2.13.0以来,它支持一个新的选项来显示根项目的路径,它甚至在从子模块内部使用时也能工作:

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


    shell框架中预先配置的shell别名

    如果使用shell框架,则可能已经存在可用的shell别名:

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


    今天必须自己解决这个问题。在C语言中解决了它,因为我需要它作为一个程序,但我想它可以很容易地重写。考虑这个公共领域。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;

    }

    如果任何人需要符合POSIX的方式来完成此操作,而不需要git可执行文件:

    git-root

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #$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替换为:

    1
    2
    3
    git_root() {
        (git_root_recurse_parent)
    }


    我想进一步阐述丹尼尔·布罗克曼的精彩评论。

    定义git config --global alias.exec '!exec '允许您执行git exec make之类的操作,因为正如man git-config所述:

    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.

    知道$GIT_PREFIX是相对于存储库的顶级目录的当前目录的路径也很方便。但是,知道这只是一半的战斗吗?.shell变量扩展使其很难使用。因此,我建议像这样使用bash -c

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

    其他命令包括:

    1
    2
    git exec pwd
    git exec make