位于目录 (cd) 和使用 `work-tree` 参数之间的 Git 有什么区别?

What is the difference in Git between being in a directory (cd) and using the `work-tree` parameter?

我在 PHP 的 exec 中使用 PHP 到 运行 Git 来获取有关某些 Git 项目的一些信息一组服务器仪表板。我遇到了一些奇怪的输出,这让我想知道我是否误解了 "working tree" 是什么。

如果我使用此命令,将 %s sprintf 参数替换为 Git 项目的路径:

git --work-tree=%s status

然后我得到这个输出:

On branch server-dashboard
Your branch is ahead of 'origin/server-dashboard' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   .gitignore
    deleted:    README.md
    deleted:    common.php
    deleted:    conf/dev/settings.ini
    deleted:    conf/prod/settings.ini
    deleted:    conf/settings.ini.example
    deleted:    lib/HealthSettings.php
    deleted:    lib/Settings.php
    deleted:    public/assets/main.css
    deleted:    public/assets/main.js
    deleted:    public/assets/refresh.gif
    deleted:    public/assets/spinner.gif
    deleted:    public/curl-test.php
    deleted:    public/dashboard.php
    deleted:    public/iframes.php
    modified:   public/index.php
    deleted:    public/info.php
    deleted:    public/no-servers.php
    deleted:    public/sections/apache-mods.php
    deleted:    public/sections/curl-headers.php
    deleted:    public/sections/curl-self.php
    deleted:    public/sections/database.php
    deleted:    public/sections/env.php
    deleted:    public/sections/git-table.php
    deleted:    public/sections/git.php
    deleted:    public/sections/php-exts.php
    deleted:    public/sections/php-proxy.php
    deleted:    public/sections/tableau-proxy-table.php
    deleted:    public/sections/tableau-proxy.php
    deleted:    public/sections/user.php
    deleted:    public/tabs.php

这不是我所期望的,因为这不是我在控制台上 运行 git status 得到的结果。

现在,如果我 运行 在 PHP 中执行此命令(再次将字符串参数换成路径):

cd %s && git status

然后我得到正确的输出:

On branch master
Your branch is ahead of 'origin/master' by 17 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

我猜因为这个项目落后于 master 17 次提交,所以 "wrong" 输出实际上是对这些提交发生的变化的表达。但是,我原以为这两个命令是等价的。我错了吗?如果是这样,我可以 运行 以编程方式执行 Git 命令,而不必先 cd 吗?

cd <em>path</em>之间有很多实质性的区别; git <em>command</em> and git --work-tree=<em>path</em> <em>命令</em>。根据 附加 参数 and/or 环境变量,可以使部分或全部差异消失。

重要的是要认识到 Git 有三个(不仅仅是两个)几乎所有时间都必须使用的关键项目。它们是:

  • 存储库本身(name-to-hash-ID 对的 repo 数据库,例如 master 表示提交 e3331758f12da22f4103eec7efe1b5304a9be5e9 或任何其他哈希 ID,加上对象其 ID 是那个又大又丑的哈希 ID 字符串)。存储库通常位于工作树顶层名为 .git 的目录中。这是 git 目录 ($GIT_DIR).

  • 索引和缓存的 index(因此它的两个名称 index 或有时 cache) 工作树,并充当更新文件(实际上是 blob 哈希 ID t[ 的路径名=212=]slations) 当你打算构建一个新的提交时。索引主要是一个文件:.git/index。从这个路径名可以看出,默认情况下,索引文件位于存储库中。但是,它有自己单独的控制变量 $GIT_INDEX_FILE。它只是 默认 $GIT_DIR/index.

  • 工作树 保存未压缩格式的文件。文件通过从提交中提取到索引中,然后从索引中提取出来 进入 工作树(它们仍然被压缩并且仅在 Git 中格式)进入工作树。工作树还可能包含在索引中找不到的其他文件。此类文件 未暂存 。未暂存的文件可能会或可能不会被忽略(暂存的文件,即路径名出现在索引中的文件,根据定义永远不会被忽略)。

工作树通常只是当前工作目录,或者从当前工作目录向上移动(..,然后是 ../..,依此类推)找到第一个位置包含 .git 存储库目录。这意味着 cd <em>path</em>; git ... 从您登陆的地方开始搜索工作树。

如果没有覆盖,找到工作树并因此找到 .git 目录,Git 现在知道 $GIT_DIR 在哪里以及在哪里可以找到索引文件。但是,如果您提供覆盖,请使用 git --work-tree=<em>path</em> 或通过设置环境变量 $GIT_WORK_TREE, Git 将在那里寻找工作树,并在当前目录(或 .. 然后 ../.. 等等)中寻找存储库目录。

如果你提供--git-dir=<em>path</em>覆盖,或者设置环境变量$GIT_DIR, Git 将在 那里 查找存储库目录,无论工作树是否有任何设置。

(注意:--git-dir--work-tree实际上是通过让git前端设置环境变量来实现的。因此,如果你设置both,标志参数在 Git 命令期间覆盖环境设置,包括 Git 本身运行的任何子进程。)

如果您通过环境提供 $GIT_INDEX_FILE 覆盖,Git 将在 那里 查找索引文件,无论是否设置任何设置$GIT_DIR.

这些设置中的任何一个都可以是 绝对路径——在类 Unix 系统上以 / 开头,或者在更愚蠢的系统上使用驱动器号——或者 相对路径。绝对路径覆盖当前工作目录,而相对路径从当前工作目录开始。

因此,任何这些参数或环境变量的确切内容都非常重要。例如,运行:

cd $HOME/foo; GIT_INDEX_FILE=$HOME/index git --git-dir=sub/.git --work-tree=/tmp ...

将导致Git在[=34=中查找存储库索引文件$HOME/index,以及 /tmp.

中的 work-tree

除此之外,前端 git 命令允许一个 -C 个参数,或多个 -C 个参数。这些中的每一个都使 Git 对提供的路径执行 cd。因此,以上内容在很大程度上等同于:

GIT_INDEX_FILE=$HOME/index git -C $HOME/foo --git-dir=sub/.git --work-tree=/tmp ...

除了上述命令终止后,您的 shell / 命令解释器仍保留在您 运行 命令之前的任何工作目录中。 (这些细节在 Windows 上略有不同,我相信,因为 Windows 对 "current directory" 和 "current drive letter" 做出了一些非常 st运行ge 的假设,或者类似的东西.)


在您的特定情况下——运行 git status——请注意 git status 进行两个单独的比较:

  • 首先,它将当前提交(通过$GIT_DIR/HEAD找到)与$GIT_INDEX_FILE的内容进行比较(a la git diff)。这里的不同之处在于 staged for commit.
  • 然后它将索引文件的内容与$GIT_WORK_TREE 的内容进行比较(再次la git diff)。这里的不同之处在于 not staged for commit.

前面的 and/or 后面的计数来自较早的步骤,其中 git status 使用当前的 b运行ch(同样来自 $GIT_DIR/HEAD)。通常,这个 HEAD 文件是一个符号引用,包含当前 b运行ch 的名称。 Git然后可以发现b运行ch的upstream设置(git rev-parse --symbolic-full-name $branch@{upstream},或多或少,虽然--abbrev-ref更适合人类): master 通常有 refs/remotes/origin/master 作为它的上游。 Git 然后通读从 $GIT_DIR 中提取的提交图,以发现 master 之后的 and/or 与 origin/master.

相比有多少提交

以上还不是全部。查阅 git front end command documentation 以找到可用于覆盖特定项目的更完整的环境变量列表。例如,GIT_ALTERNATE_OBJECT_DIRECTORIES 可用于使 Git 在存储库本身之外查找其他对象存储位置,而 GIT_CEILING_DIRECTORIES 可用于限制路径遍历的数量 Git 在查看 ..../.. 等时执行。

另一个答案通过讨论在 运行 一个 php 服务器脚本时永远不会在实践中设置的环境变量使事情变得复杂,并且它没有明确回答哪个选项可以解决问题。让我们试试:

$ cd
$ ls -ad .git
ls: cannot access '.git': No such file or directory

$ env | grep -i git | wc -l
0

$ git --work-tree=$HOME/myrepo describe --always
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).

$ git --work-tree=$HOME/myrepo --git-dir=$HOME/myrepo/.git describe --always
d5cd316

$ git --git-dir=$HOME/myrepo/.git describe --always
d5cd316

$ git --git-dir=$HOME/myrepo describe --always
fatal: not a git repository: '/home/redacted/myrepo'

--> --git-dir 就足够了,但它需要 /.git 后缀。