位于目录 (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
后缀。
我在 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.
除此之外,前端 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 lagit diff
)。这里的不同之处在于 staged for commit. - 然后它将索引文件的内容与
$GIT_WORK_TREE
的内容进行比较(再次lagit 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
后缀。