設定和配置
獲取和建立專案
基本快照
分支與合併
共享和更新專案
檢查和比較
打補丁
除錯
電子郵件
外部系統
伺服器管理
指南
管理
底層命令
- 2.43.1 → 2.50.1 無更改
-
2.43.0
2023-11-20
- 2.38.1 → 2.42.4 無更改
-
2.38.0
2022-10-02
描述
Git 支援透過 ssh://、git://、http:// 和 file:// 傳輸方式傳輸打包檔案中的資料。存在兩組協議,一組用於將資料從客戶端推送到伺服器,另一組用於從伺服器獲取資料到客戶端。這三種傳輸方式(ssh、git、file)使用相同的協議來傳輸資料。http 傳輸方式在 gitprotocol-http[5] 中有文件說明。
在標準的 Git 實現中,獲取資料時在伺服器端呼叫 upload-pack 程序,在客戶端呼叫 fetch-pack 程序;推送資料時在伺服器端呼叫 receive-pack 程序,在客戶端呼叫 send-pack 程序。協議的功能是讓伺服器告訴客戶端伺服器上當前有什麼,然後雙方協商傳送最少量的資料以完全更新其中一方。
pkt-line 格式
以下描述基於 gitprotocol-common[5] 中描述的 pkt-line 格式。當語法指示 PKT-LINE
(...
) 時,除非另有說明,否則適用通常的 pkt-line 換行規則:傳送方應包含一個 LF,但接收方即使不存在也絕不能報錯。
錯誤包是一種特殊的 pkt-line,其中包含錯誤字串。
error-line = PKT-LINE("ERR" SP explanation-text)
在整個協議中,當預期 PKT-LINE
(...
) 時,可以傳送錯誤包。一旦客戶端或伺服器傳送此包,此協議中定義的資料傳輸過程即終止。
傳輸方式
打包檔案協議透過三種傳輸方式啟動。Git 傳輸方式是一個簡單的、未經身份驗證的伺服器,它接收客戶端希望通訊的命令(幾乎總是 upload-pack,儘管 Git 伺服器可以配置為全域性可寫,在這種情況下也允許啟動 receive-pack),並執行該命令並將其連線到請求程序。
在 SSH 傳輸方式中,客戶端僅透過 SSH 協議在伺服器上執行 upload-pack 或 receive-pack 程序,然後透過 SSH 連線與該呼叫程序通訊。
file:// 傳輸方式在本地執行 upload-pack 或 receive-pack 程序,並透過管道與其通訊。
額外引數
協議提供了一種機制,客戶端可以在其第一個訊息中向伺服器傳送額外資訊。這些被稱為“額外引數”,並受 Git、SSH 和 HTTP 協議支援。
每個額外引數的形式為 <key>=
<value> 或 <key>。
收到任何此類額外引數的伺服器必須忽略所有無法識別的鍵。目前,唯一識別的額外引數是“version”,其值為 1 或 2。有關協議版本 2 的更多資訊,請參閱 gitprotocol-v2[5]。
Git 傳輸
Git 傳輸透過 pkt-line 格式在網路上傳送命令和倉庫,後跟一個 NUL 位元組和一個主機名引數,以 NUL 位元組終止。
0033git-upload-pack /project.git\0host=myserver.com\0
傳輸可以傳送額外引數,方法是新增一個額外的 NUL 位元組,然後新增一個或多個以 NUL 終止的字串。
003egit-upload-pack /project.git\0host=myserver.com\0\0version=1\0
git-proto-request = request-command SP pathname NUL [ host-parameter NUL ] [ NUL extra-parameters ] request-command = "git-upload-pack" / "git-receive-pack" / "git-upload-archive" ; case sensitive pathname = *( %x01-ff ) ; exclude NUL host-parameter = "host=" hostname [ ":" port ] extra-parameters = 1*extra-parameter extra-parameter = 1*( %x01-ff ) NUL
主機引數用於基於 git-daemon 名稱的虛擬主機。請參閱 git daemon 的 --interpolated-path 選項,帶有 %H/%CH 格式字元。
基本上,Git 客戶端透過 Git 協議連線到伺服器端的 upload-pack 程序的操作如下:
$ echo -e -n \ "003agit-upload-pack /schacon/gitbook.git\0host=example.com\0" | nc -v example.com 9418
SSH 傳輸
透過 SSH 啟動 upload-pack 或 receive-pack 程序是透過 SSH 遠端執行在伺服器上執行二進位制檔案。這基本上等同於執行以下命令:
$ ssh git.example.com "git-upload-pack '/project.git'"
對於伺服器透過 SSH 支援給定使用者的 Git 推送和拉取,該使用者需要能夠透過登入時提供的 SSH shell 執行這兩個命令中的一個或兩個。在某些系統上,該 shell 訪問僅限於能夠執行這兩個命令,甚至只有一個。
在 ssh:// 格式的 URI 中,它是 URI 中的絕對路徑,因此主機名(或埠號)後的 / 作為引數傳送,然後遠端 git-upload-pack 完全按原樣讀取,因此它實際上是遠端檔案系統中的絕對路徑。
git clone ssh://user@example.com/project.git | v ssh user@example.com "git-upload-pack '/project.git'"
在“user@host:path”格式的 URI 中,它是相對於使用者主目錄的,因為 Git 客戶端將執行:
git clone user@example.com:project.git | v ssh user@example.com "git-upload-pack 'project.git'"
例外情況是如果使用了 ~,在這種情況下我們會在沒有開頭的 / 的情況下執行它。
ssh://user@example.com/~alice/project.git, | v ssh user@example.com "git-upload-pack '~alice/project.git'"
根據 protocol.version
配置變數的值,Git 可能會嘗試將額外引數作為冒號分隔的字串傳送到 GIT_PROTOCOL 環境變數中。只有當 ssh.variant
配置變數指示 ssh 命令支援將環境變數作為引數傳遞時,才會這樣做。
這裡有幾點需要記住:
-
“命令名”用連字元拼寫(例如 git-upload-pack),但這可以被客戶端覆蓋;
-
倉庫路徑總是用單引號引用。
從伺服器獲取資料
當一個 Git 倉庫想要獲取第二個倉庫中的資料時,第一個倉庫可以從第二個倉庫中進行 fetch(拉取)。此操作確定伺服器上客戶端沒有哪些資料,然後以打包檔案格式將這些資料流式傳輸到客戶端。
引用發現
當客戶端初次連線時,伺服器將立即響應一個版本號(如果“version=1”作為額外引數傳送),以及它擁有的每個引用(所有分支和標籤)的列表,以及每個引用當前指向的物件名稱。
$ echo -e -n "0045git-upload-pack /schacon/gitbook.git\0host=example.com\0\0version=1\0" | nc -v example.com 9418 000eversion 1 00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag 00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration 003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 refs/heads/master 003cb88d2441cac0977faf98efc80305012112238d9d refs/tags/v0.9 003c525128480b96c89e6418b1e40909bf6c5b2d580f refs/tags/v1.0 003fe92df48743b7bc7d26bcaabfddde0a1e20cae47c refs/tags/v1.0^{} 0000
返回的響應是描述每個引用及其當前值的 pkt-line 流。該流必須按照 C 語言環境的排序規則按名稱排序。
如果 HEAD 是一個有效引用,HEAD 必須作為第一個 advertised ref 出現。如果 HEAD 不是一個有效引用,HEAD 絕不能出現在廣告列表中,但其他引用仍可能出現。
該流必須在第一個引用上的 NUL 後面包含能力宣告。一個引用的 peeled 值(即“ref^{}”)必須緊隨引用本身之後,如果存在的話。符合規範的伺服器如果引用是一個帶註解的標籤,則必須 peel 該引用。
advertised-refs = *1("version 1") (no-refs / list-of-refs) *shallow flush-pkt no-refs = PKT-LINE(zero-id SP "capabilities^{}" NUL capability-list) list-of-refs = first-ref *other-ref first-ref = PKT-LINE(obj-id SP refname NUL capability-list) other-ref = PKT-LINE(other-tip / other-peeled) other-tip = obj-id SP refname other-peeled = obj-id SP refname "^{}" shallow = PKT-LINE("shallow" SP obj-id) capability-list = capability *(SP capability) capability = 1*(LC_ALPHA / DIGIT / "-" / "_") LC_ALPHA = %x61-7A
伺服器和客戶端必須對 obj-id 使用小寫,兩者都必須將 obj-id 視為大小寫不敏感。
有關允許的伺服器能力列表和描述,請參閱 protocol-capabilities.txt。
打包檔案協商
在引用和能力發現之後,客戶端可以透過傳送 flush-pkt 來終止連線,告知伺服器在不需要任何打包資料時可以優雅地終止並斷開連線。這可能會發生在 ls-remote 命令中,也可能發生在客戶端已經是最新的情況下。
否則,它將進入協商階段,客戶端和伺服器透過告知伺服器它想要的物件、它的淺層物件(如果有)以及它想要的最大提交深度(如果有),來確定傳輸所需的最小打包檔案。客戶端還將傳送一份它希望生效的能力列表,這些能力是伺服器在第一行 want 中說明它可以執行的。
upload-request = want-list *shallow-line *1depth-request [filter-request] flush-pkt want-list = first-want *additional-want shallow-line = PKT-LINE("shallow" SP obj-id) depth-request = PKT-LINE("deepen" SP depth) / PKT-LINE("deepen-since" SP timestamp) / PKT-LINE("deepen-not" SP ref) first-want = PKT-LINE("want" SP obj-id SP capability-list) additional-want = PKT-LINE("want" SP obj-id) depth = 1*DIGIT filter-request = PKT-LINE("filter" SP filter-spec)
客戶端必須將它在引用發現階段想要的所有 obj-id 作為 want 行傳送。客戶端必須在請求正文中傳送至少一個 want 命令。客戶端絕不能在 want 命令中提及透過引用發現未出現在響應中的 obj-id。
客戶端必須將所有它只有淺層副本(即沒有提交的父物件)的 obj-id 寫入 shallow 行,以便伺服器瞭解客戶端歷史的限制。
客戶端現在傳送它希望此事務的最大提交歷史深度,即從歷史頂端算起的提交數量(如果有),作為 deepen 行。深度為 0 與不發出深度請求相同。客戶端不希望收到超過此深度的任何提交,也不希望收到僅為完成這些提交所需的任何物件。因此未收到的父提交被定義為淺層提交,並在伺服器中標記為淺層。此資訊將在下一步中傳送回客戶端。
客戶端可以選擇使用幾種過濾技術之一,要求 pack-objects 從打包檔案中省略各種物件。這些技術旨在用於部分克隆和部分抓取操作。除非在 want 行中明確請求,否則不符合 filter-spec 值(見 rev-list
以獲取可能的 filter-spec 值)的物件將被省略。
一旦所有的 want 和 shallow(以及可選的 deepen)傳輸完畢,客戶端必須傳送一個 flush-pkt,以告知伺服器端它已完成列表的傳送。
否則,如果客戶端傳送了一個正的深度請求,伺服器將確定哪些提交是淺層的,哪些不是,並將此資訊傳送給客戶端。如果客戶端沒有請求正深度,則跳過此步驟。
shallow-update = *shallow-line *unshallow-line flush-pkt shallow-line = PKT-LINE("shallow" SP obj-id) unshallow-line = PKT-LINE("unshallow" SP obj-id)
如果客戶端請求了正深度,伺服器將計算一組不深於所需深度的提交。這組提交從客戶端的 wants 開始。
伺服器為每個其父物件不會被髮送的提交寫入 shallow 行。伺服器為每個客戶端已指示為淺層但當前請求深度下不再淺層(即其父物件現在將被髮送)的提交寫入 unshallow 行。伺服器不得將客戶端未指示為淺層的任何內容標記為 unshallow。
現在客戶端將使用 have 行傳送它擁有的 obj-id 列表,以便伺服器可以建立一個只包含客戶端所需物件的打包檔案。在 multi_ack 模式下,標準實現將一次傳送多達 32 個這樣的 obj-id,然後傳送一個 flush-pkt。標準實現將立即跳到下一個 32 個併發送,以便始終有 32 個塊“在空中傳輸”。
upload-haves = have-list compute-end have-list = *have-line have-line = PKT-LINE("have" SP obj-id) compute-end = flush-pkt / PKT-LINE("done")
如果伺服器讀取 have 行,它將透過確認客戶端聲稱擁有的、伺服器也擁有的任何 obj-id 來響應。伺服器將根據客戶端選擇的確認模式以不同的方式確認 obj-id。
在 multi_ack 模式下
-
伺服器將對任何共同的提交響應 ACK obj-id continue。
-
一旦伺服器找到一個可接受的共同基礎提交併準備建立打包檔案,它將盲目地將所有 have obj-id 返回給客戶端進行確認。
-
伺服器隨後將傳送一個 NAK,然後等待客戶端的另一個響應——要麼是 done,要麼是另一組 have 行。
在 multi_ack_detailed 模式下
-
伺服器將區分它傳送資料訊號的 ACK,使用 ACK obj-id ready 行,並透過 ACK obj-id common 行傳送已識別的共同提交。
在沒有 multi_ack 或 multi_ack_detailed 的情況下
-
upload-pack 在找到第一個共同物件時傳送“ACK obj-id”。此後,在客戶端給出“done”之前,它不會再發送任何訊息。
-
如果在 flush-pkt 時尚未找到共同物件,upload-pack 會發送“NAK”。如果已找到共同物件,並且已傳送 ACK,則在 flush-pkt 時保持沉默。
在客戶端收到足夠的 ACK 響應,可以確定伺服器有足夠的資訊來發送高效的打包檔案時(在標準實現中,這在它收到足夠多的 ACK,使得 --date-order 佇列中剩餘的所有內容都可以與伺服器共用,或者 --date-order 佇列為空時確定),或者客戶端確定它想要放棄時(在標準實現中,這在客戶端傳送 256 行 have 而沒有收到伺服器的任何 ACK 時確定——這意味著沒有任何共同點,伺服器應該只發送它所有的物件),然後客戶端將傳送一個 done 命令。done 命令向伺服器發出訊號,表明客戶端已準備好接收其打包檔案資料。
然而,256 的限制僅在標準客戶端實現中,當我們在前一輪中至少收到一個“ACK %s continue”時才開啟。這有助於確保在完全放棄之前至少找到一個共同祖先。
一旦從客戶端讀取到 done 行,伺服器將傳送最終的 ACK obj-id 或傳送一個 NAK。obj-id 是確定為共同的最後一個提交的物件名稱。伺服器僅在 done 之後傳送 ACK,如果至少有一個共同基礎且 multi_ack 或 multi_ack_detailed 已啟用。如果沒有找到共同基礎,伺服器始終在 done 之後傳送 NAK。
伺服器除了傳送 ACK 或 NAK 之外,還可能傳送錯誤訊息(例如,如果它無法識別客戶端收到的 want 行中的物件)。
然後伺服器將開始傳送其打包檔案資料。
server-response = *ack_multi ack / nak ack_multi = PKT-LINE("ACK" SP obj-id ack_status) ack_status = "continue" / "common" / "ready" ack = PKT-LINE("ACK" SP obj-id) nak = PKT-LINE("NAK")
一個簡單的克隆可能看起來像這樣(沒有 have 行)
C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack \ side-band-64k ofs-delta\n C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n C: 0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n C: 0032want 74730d410fcb6603ace96f1dc55ea6196122532d\n C: 0000 C: 0009done\n S: 0008NAK\n S: [PACKFILE]
增量更新(fetch)響應可能看起來像這樣
C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack \ side-band-64k ofs-delta\n C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n C: 0000 C: 0032have 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n C: [30 more have lines] C: 0032have 74730d410fcb6603ace96f1dc55ea6196122532d\n C: 0000 S: 003aACK 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01 continue\n S: 003aACK 74730d410fcb6603ace96f1dc55ea6196122532d continue\n S: 0008NAK\n C: 0009done\n S: 0031ACK 74730d410fcb6603ace96f1dc55ea6196122532d\n S: [PACKFILE]
打包檔案資料
現在客戶端和伺服器已經完成關於需要傳送給客戶端的最小資料量的協商,伺服器將構建併發送打包檔案格式所需的D資料。
關於打包檔案本身實際的樣子,請參閱 gitformat-pack[5]。
如果客戶端指定了 side-band 或 side-band-64k 能力,伺服器將多路複用傳送打包檔案資料。
每個資料包以跟隨資料的資料量 pkt-line 長度開始,後跟一個單位元組,指定跟隨資料所屬的 sideband。
在 side-band 模式下,它將傳送最多 999 位元組的資料加上 1 個控制碼,總計最多 1000 位元組在一個 pkt-line 中。在 side-band-64k 模式下,它將傳送最多 65519 位元組的資料加上 1 個控制碼,總計最多 65520 位元組在一個 pkt-line 中。
sideband 位元組將是 1、2 或 3。Sideband 1 將包含打包檔案資料,sideband 2 將用於客戶端通常列印到 stderr 的進度資訊,sideband 3 用於錯誤資訊。
如果未指定 side-band 能力,伺服器將不經多路複用直接流式傳輸整個打包檔案。
向伺服器推送資料
向伺服器推送資料將呼叫伺服器上的 receive-pack 程序,該程序將允許客戶端告訴它應該更新哪些引用,然後傳送伺服器完成這些新引用所需的所有資料。一旦所有資料都被接收和驗證,伺服器將更新其引用以匹配客戶端指定的內容。
身份驗證
協議本身不包含身份驗證機制。這應在呼叫 receive-pack 程序之前由傳輸(如 SSH)處理。如果 receive-pack 透過 Git 傳輸配置,則任何可以訪問該埠(9418)的人都可以寫入這些倉庫,因為該傳輸是未經身份驗證的。
引用發現
引用發現階段的執行方式與抓取協議中的方式幾乎相同。伺服器上的每個引用 obj-id 和名稱都以 packet-line 格式傳送到客戶端,後跟一個 flush-pkt。唯一真正的區別是能力列表不同——唯一可能的值是 report-status, report-status-v2, delete-refs, ofs-delta, atomic 和 push-options。
引用更新請求和打包檔案傳輸
一旦客戶端知道伺服器上的引用情況,它就可以傳送一個引用更新請求列表。對於伺服器上它想要更新的每個引用,它會發送一行,列出伺服器上當前存在的 obj-id、客戶端希望將其更新到的 obj-id 以及引用的名稱。
此列表後跟一個 flush-pkt。
update-requests = *shallow ( command-list | push-cert ) shallow = PKT-LINE("shallow" SP obj-id) command-list = PKT-LINE(command NUL capability-list) *PKT-LINE(command) flush-pkt command = create / delete / update create = zero-id SP new-id SP name delete = old-id SP zero-id SP name update = old-id SP new-id SP name old-id = obj-id new-id = obj-id push-cert = PKT-LINE("push-cert" NUL capability-list LF) PKT-LINE("certificate version 0.1" LF) PKT-LINE("pusher" SP ident LF) PKT-LINE("pushee" SP url LF) PKT-LINE("nonce" SP nonce LF) *PKT-LINE("push-option" SP push-option LF) PKT-LINE(LF) *PKT-LINE(command LF) *PKT-LINE(gpg-signature-lines LF) PKT-LINE("push-cert-end" LF) push-option = 1*( VCHAR | SP )
如果伺服器已宣告 push-options 能力,並且客戶端已將 push-options 作為上述能力列表的一部分指定,則客戶端隨後傳送其推送選項,後跟一個 flush-pkt。
push-options = *PKT-LINE(push-option) flush-pkt
為了與舊版 Git 伺服器保持向後相容性,如果客戶端傳送推送證書和推送選項,則必須在推送證書中嵌入推送選項,並在推送證書後再次傳送推送選項。(請注意,證書中的推送選項帶有字首,而證書後的推送選項則沒有。)這兩份列表必須相同,僅字首有所不同。
之後,將傳送包含伺服器完成新引用所需的所有物件的打包檔案。
packfile = "PACK" 28*(OCTET)
如果接收端不支援 delete-refs,則傳送端絕不能請求 delete 命令。
如果接收端不支援 push-cert,傳送端絕不能傳送 push-cert 命令。當傳送 push-cert 命令時,絕不能傳送 command-list;而是使用推送證書中記錄的命令。
如果僅使用 delete 命令,則絕不能傳送打包檔案。
如果使用了 create 或 update 命令,即使伺服器已經擁有所有必需的物件,也必須傳送打包檔案。在這種情況下,客戶端必須傳送一個空的打包檔案。這種情況唯一可能發生的是客戶端正在建立一個指向現有 obj-id 的新分支或標籤。
伺服器將接收打包檔案,解包,然後驗證正在更新的每個引用在請求處理過程中是否未更改(obj-id 仍然與 old-id 相同),並且它將執行任何更新鉤子以確保更新是可接受的。如果一切正常,伺服器將更新引用。
推送證書
推送證書以一組頭行開始。在頭和空行之後,是協議命令,每行一個。請注意,push-cert PKT-LINE 中的尾隨 LF 不是可選的;它必須存在。
目前,定義了以下頭部欄位:
GPG 簽名行是簽名塊開始之前,推送證書中記錄內容的獨立簽名。獨立簽名用於證明這些命令是由推送者(必須是簽名者)發出的。
報告狀態
從傳送方收到打包資料後,如果 report-status 或 report-status-v2 能力生效,接收方將傳送一份報告。這是一份簡短的更新摘要。它將首先列出打包檔案解包的狀態,可以是 unpack ok 或 unpack [error]。然後它將列出它嘗試更新的每個引用的狀態。每行要麼是 ok [refname](如果更新成功),要麼是 ng [refname] [error](如果更新失敗)。
report-status = unpack-status 1*(command-status) flush-pkt unpack-status = PKT-LINE("unpack" SP unpack-result) unpack-result = "ok" / error-msg command-status = command-ok / command-fail command-ok = PKT-LINE("ok" SP refname) command-fail = PKT-LINE("ng" SP refname SP error-msg) error-msg = 1*(OCTET) ; where not "ok"
report-status-v2 功能透過新增新的選項行來擴充套件協議,以支援報告由 proc-receive 鉤子重寫的引用。proc-receive 鉤子可能處理偽引用的命令,該命令可能建立或更新一個或多個引用,並且每個引用可能具有不同的名稱、不同的 new-oid 和不同的 old-oid。
report-status-v2 = unpack-status 1*(command-status-v2) flush-pkt unpack-status = PKT-LINE("unpack" SP unpack-result) unpack-result = "ok" / error-msg command-status-v2 = command-ok-v2 / command-fail command-ok-v2 = command-ok *option-line command-ok = PKT-LINE("ok" SP refname) command-fail = PKT-LINE("ng" SP refname SP error-msg) error-msg = 1*(OCTET) ; where not "ok" option-line = *1(option-refname) *1(option-old-oid) *1(option-new-oid) *1(option-forced-update) option-refname = PKT-LINE("option" SP "refname" SP refname) option-old-oid = PKT-LINE("option" SP "old-oid" SP obj-id) option-new-oid = PKT-LINE("option" SP "new-oid" SP obj-id) option-force = PKT-LINE("option" SP "forced-update")
更新失敗的原因有很多。引用可能自引用發現階段最初發送以來已更改,這意味著在此期間有人進行了推送。正在推送的引用可能不是快進引用,並且更新鉤子或配置可能設定為不允許這種情況,等等。此外,某些引用可以更新,而另一些則可能被拒絕。
一個客戶端/伺服器通訊的例子可能看起來像這樣:
S: 006274730d410fcb6603ace96f1dc55ea6196122532d refs/heads/local\0report-status delete-refs ofs-delta\n S: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug\n S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/master\n S: 003d74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/team\n S: 0000 C: 00677d1665144a3a975c05f1f43902ddaf084e784dbe 74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/debug\n C: 006874730d410fcb6603ace96f1dc55ea6196122532d 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a refs/heads/master\n C: 0000 C: [PACKDATA] S: 000eunpack ok\n S: 0018ok refs/heads/debug\n S: 002ang refs/heads/master non-fast-forward\n