簡體   English   中英

是否可以在 git 存儲庫之外添加和提交文件?

[英]Is it possible to add and commit files outside of a git repository?

我們在系統的各個角落都有文本文件,我們計划將在這些文件中所做的所有修改添加到 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

答案是否定的,也是。 1

如果你打算只使用 Git “瓷器”命令,那很明顯是“不”,因為這些命令與一個(單個)工作樹的概念一起工作,它包含所有正常格式的文件,加上一個索引(保存當前狀態)該工作樹並構建下一個提交)。 有一個HEAD文件保存了當前分支名稱的概念。 您至少需要兩個單獨的瓷器命令,按以下順序:

git add <path>
git commit <arguments>

<path>文件的(單個)工作樹版本更新(單個)索引,然后使用該索引和當前HEAD進行提交。 Git 會在提交時對它更新的內容進行一些鎖定,但是您需要添加然后提交序列以顯示原子性,因此您需要將自己的鎖定堆積在這些之上。

(即使您使用--work-tree和/或--git-dir參數來重定向各個步驟的各個部分,這仍然是正確的:共享索引必須在“添加”和“提交”步驟之間保持穩定。)

另一方面,如果你願意擺脫純瓷器的舒適,你可以將提交本身作為一個原子實體完成——但你仍然在看一場比賽,所以你需要在提交之前解決這個問題答案確實從“否”變為“是”。 要了解這是如何工作的,我們必須將git addgit commit步驟分開。

首先, git add本質上是git update-index 我們可以創建一個新的、臨時的、私有的索引,並從我們選擇的一些特定提交中填充它:

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-indexgit 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>

可選的-m <reason>部分存儲在引用日志中,如果此引用有引用日志。 (此步驟還使用user.nameuser.email ,因此如果需要,請在此處提供它們。) refname部分是引用的全名,例如, refs/heads/branch表示分支branch newvalue部分是我們要存儲的哈希 ID,而oldvalue部分(我們將提供以檢查競爭)是我們希望分支名稱立即存儲的值。

現在,假設我們正在與其他一些進程競爭,有兩種可能的情況:

  • 我們贏得了比賽:我們在開始時讀取的樹是與當前位於分支尖端的提交相關的樹。 因此,我們的提交已准備好以簡單的線性方式添加到分支中。

或者:

  • 我們輸了比賽:我們在開始時讀取的樹是有效的,但分支名稱現在指向更新的提交。 因此,我們的提交毫無價值,或者需要放在一個分支上,或者其他什么。 如果我們的承諾真的毫無價值,我們可以重新開始並重新做整個事情:也許這一次我們會贏得比賽。

如何處理“失去比賽”的情況取決於您。 但是現在我們看到了“魔法”的來源:當我們開始整個過程​​時,我們想要的提交 ID 是與引用關聯的當前提交哈希。 所以“魔法”只是:

commit_id=$(git rev-parse $refname)

它讀取引用的當前值(如果它是一個分支名稱,我們可以假設底層對象的類型是commit )。

由於update-ref一步都有自己的原子性(通過鎖定執行),這就是我們得到我們的原子。 但是,遇到失敗該怎么辦的問題是最難的部分。 還要記住要考慮並處理每個中間步驟的失敗,例如,如果git rev-parse失敗,或者git read-treegit write-treegit commit-tree失敗。


1不要向精靈尋求建議,因為他們會說不和是。

不,這不可能。 您可以考慮創建一個巨大的 git 存儲庫、多個 git 存儲庫,重新考慮您的文件結構以將其全部包含在一個目錄中(如果可能),或者使用 Docker 之類的東西來創建整個計算機的映像。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM