我们在系统的所有角落都有文本文件,我们计划将这些文件中的所有修改添加到git存储库。
每次对这些文件进行修改时,都会通过脚本进行修改。因此,我们计划向该脚本添加新命令,以将文件添加到git存储库。但是,这些修改是并发的。
我们可以从原始路径构建表示原始位置的每个文件的路径。
是否可以同时将这些文件添加到git存储库?
类似于加入add + commit并指向两者的原子操作:外部文件路径及其存储库对应路径。类似的东西:
git --user="Script1 <script1@localhost>" --git-dir=/home/repo/filescollection.git/.git add --external-path=/home/user1/file.txt --repo-path=home_user1_files.txt
答案 0 :(得分:3)
答案是否和是。 1
如果你打算只使用Git&#34;瓷器&#34;命令,它非常清楚&#34; no&#34;,因为它们与(单个)工作树的概念一起工作,它包含所有正常格式文件,加上一个 index (保存该工作树的当前状态并构建下一个提交)。有一个HEAD
文件包含当前分支名称的概念。您需要至少两个单独的瓷器命令,按此顺序:
git add <path>
git commit <arguments>
从<path>
中的文件的(单个)工作树版本更新(单个)索引,然后使用该索引和当前HEAD
进行提交。 Git会对提交时更新的内容进行一些锁定,但是你需要add-then-commit序列才能显示原子,所以你需要在这些上面建立自己的锁定。
(即使您使用--work-tree
和/或--git-dir
参数重定向各个步骤的各个部分,这仍然是正确的:共享索引必须在&#34;添加&#34;之间保持稳定。和&#34;提交&#34;步骤。)
另一方面,如果你愿意走出纯瓷的舒适之处,那么可以将提交本身作为一个原子实体来完成 - 但你仍然在寻找一个各种各样的种族,所以你需要在答案真正从'#34; no&#34;到&#34;是&#34;。要了解其工作原理,我们必须将git add
和git commit
步骤分开。
首先,git add
基本上是git update-index
。我们可以创建一个 new ,临时私有索引,并从我们选择的某个特定提交中填充它:
commit_id=...insert some magic here, see below...
export GIT_INDEX_FILE=$(mktemp) # remember to clean it up later too
git read-tree $commit_id
现在我们可以使用git update-index
替换该索引中的任何给定文件(或者实际上,更熟悉和更舒适git add
:环境变量也可以在那里工作)。因为这是我们自己的私有索引,所以它与可能正在修改任何其他索引的所有其他进程绝缘。
现在我们可以执行git commit
所做的步骤:
tree_id=$(git write-tree)
这会将索引(现在是我们的临时索引)转换为新的顶级树,其中包含任何子目录的子树,所有这些都基于我们之前读入索引的内容(使用git read-tree
)并更新(使用git update-index
或git add
)。此顶级树以及尚未存储在存储库中的任何必需子树现在存储在存储库中。对于配置的到期时间(默认为14天),新对象对于自动git gc
是安全的,因此这是我们完成提交所需的时间。该命令将新树的ID打印到其标准输出,我们在$tree_id
变量中捕获。
接下来,我们需要使用适当的父哈希来编写一个提交对象,引用我们刚刚创建的树。正确的父哈希显然是$commit_id
。我们必须构造一个提交消息,然后运行:
new=$(git commit-tree -p $commit_id $tree_id < message_file)
或类似的。这会将提交对象写入存储库,就像git write-tree
一样,打印新对象的ID,我们将其捕获到$new
中。 (请注意,此步骤使用作者和提交者名称和电子邮件,您可以将其作为-c user.name=...
和-c user.email=...
个参数提供。)
最后,也是最重要的,我们已准备好记录这个新对象。这是我们必须解决我们的竞争的问题(每个对象编写步骤都有自己的锁定,以确保 部分是适当的原子)。
我假设您希望将这些分支名称存储在某些分支名称下,并且这些分支名称可以由其他进程读取并更新。 (如果它们是只读的,从不更新任何其他内容,我们现在可以免费使用。)我们有一个原子更新操作,形式为git update-ref
:
git update-ref [-m <reason>] <refname> <newvalue> <oldvalue>
如果有此引用的reflog,则可选的-m <reason>
部分存储在reflog中。 (此步骤也使用user.name
和user.email
,因此,如果需要,请在此处提供。) refname 部分是参考的全名,例如,refs/heads/branch
用于分支branch
。 newvalue 部分是我们要存储的哈希ID, oldvalue 部分 - 我们将提供以检查比赛 - 是我们期望存储的分支名称的值现在。
现在,假设我们 竞争其他一些过程,有两种可能的情况:
或:
如何处理&#34;输掉比赛&#34;案件取决于你。但是现在我们看到&#34;魔术&#34;来自:我们想要的提交ID,当我们开始整个过程时,是与引用关联的当前提交哈希。所以&#34;魔术&#34;只是:
commit_id=$(git rev-parse $refname)
读取引用的当前值(如果它是分支名称,我们可以假设基础对象的类型是commit
)。
由于update-ref
步骤具有自己的原子性(通过锁定强制执行),因此我们得到我们的原子性。然而,关于如何处理失败的问题是困难的部分。还要记住在每个中间步骤中考虑并做一些事情,例如,如果git rev-parse
失败,或git read-tree
或git write-tree
或git commit-tree
中的任何一个失败,
1 Go not to the Elves for counsel, for they will say both no and yes.
答案 1 :(得分:0)
不,这是不可能的。您可以考虑创建一个巨大的git存储库,多个git存储库,重新考虑您的文件结构以将其全部包含在一个目录中(如果可能),或者使用Docker之类的东西来创建整个计算机的映像。