自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何編寫一個(gè)全新的 Git 協(xié)議

開發(fā) 開發(fā)工具
曾幾何時(shí),我在持續(xù)追蹤自己的文件方面遇到一些問題。通常,我忘了自己是否將文件保存在自己的桌面電腦、筆記本電腦或者電話上,或者保存在了云上的什么地方。更有甚者,對(duì)非常重要的信息,像密碼和Bitcoin的密匙,僅以純文本郵件的形式將它發(fā)送給自己讓我芒刺在背。

曾幾何時(shí),我在持續(xù)追蹤自己的文件方面遇到一些問題。通常,我忘了自己是否將文件保存在自己的桌面電腦、筆記本電腦或者電話上,或者保存在了云上的什么地方。更有甚者,對(duì)非常重要的信息,像密碼和Bitcoin的密匙,僅以純文本郵件的形式將它發(fā)送給自己讓我芒刺在背。

我需要的是將自己的數(shù)據(jù)存放一個(gè)git倉庫里,然后將這個(gè)git倉庫保存在一個(gè)地方。我可以查看以前的版本而且不用提心數(shù)據(jù)被刪除。更最要的是,我已經(jīng)能熟練地在不同電腦上使用git來上傳和下載文件。

但是,如我所言,我并不想簡(jiǎn)單地上傳我的密匙和密碼到GitHub或者BitBucket,哪怕是其中的私有倉庫。

一個(gè)很酷的想法在我腦中生騰:寫一個(gè)工具來加密我的倉庫,然后再將它Push到Backup。遺憾的是,不能像平時(shí)那樣使用 git push命令,需要使用像這樣的命令:

$ encrypted-git push http://example.com/

至少,在我發(fā)現(xiàn)git-remote-helpers以前是這樣想的。

Git remote helpers

我在網(wǎng)上找到一篇git remote helpers的文檔。

原來,如果你運(yùn)行命令

$ git remote add origin asdf://example.com/repo
$ git push --all origin

Git會(huì)首先檢查是否內(nèi)建了asdf協(xié)議,當(dāng)發(fā)現(xiàn)沒有內(nèi)建時(shí),它會(huì)檢查git-remote-asdf是否在PATH(環(huán)境變量)里,如果在,它會(huì)運(yùn)行 git-remote-asdf origin asdf://example.com/repo 來處理本次會(huì)話。

同樣的,你可以運(yùn)行

$ git clone asdf::http://example.com/repo

來讓git調(diào)用 git-remote-asdf origin http://example.com/repo.

很遺憾的是,我發(fā)現(xiàn)文檔在真正實(shí)現(xiàn)一個(gè)helper的細(xì)節(jié)上語焉不詳,而這正是我需要的。但是隨后,我在Git源碼中找到了一個(gè)叫g(shù)it- remote-testgit.sh的腳本,它實(shí)現(xiàn)了一個(gè)用來測(cè)試git遠(yuǎn)程輔助系統(tǒng)的testgit。 它基本實(shí)現(xiàn)了從同樣文件系統(tǒng)的本地倉庫推送和抓取功能。所以

git clone testgit::/existing-repository

git clone /existing-repository

就一樣了。

同樣地,你可以透過testgit協(xié)議向本地倉庫中推送或者從中抓取。

在本文件中,我們將瀏覽git-remote-testgit的源碼并以Go語言實(shí)現(xiàn)一個(gè)全新的helper分支: git-remote-go。過程中,我將解釋源碼的意思,以及在實(shí)現(xiàn)我自己的remote helper(git-remote-grave)中領(lǐng)悟到的種種.

基礎(chǔ)知識(shí)

為了后面的章節(jié)理解方面,讓我們先學(xué)習(xí)一些術(shù)語和基本機(jī)制。

當(dāng)我們運(yùn)行

$ git remote add myremote go::http://example.com/repo
$ git push myremote master

Git會(huì)運(yùn)行以下命令來實(shí)例化一個(gè)新的進(jìn)程

git-remote-go myremote http://example.com/repo

注意:***個(gè)參數(shù)是remote name,第二個(gè)參數(shù)是url.

當(dāng)你運(yùn)行

$ git clone go::http://example.com/repo

下一條命令會(huì)實(shí)例化helper

git-remote-go origin http://example.com/repo

因?yàn)檫h(yuǎn)程origin會(huì)自動(dòng)在克隆的倉庫中自動(dòng)創(chuàng)建。

當(dāng)Git以一個(gè)新的進(jìn)程實(shí)例化helper時(shí),它會(huì)為 stdin,stdout及stderr通信打開管道。命令被通過stdin送達(dá)helper,helper通過stdout響應(yīng)。任何helper在 stderr上的輸出被重定向到git的stderr(它可能是一個(gè)終端)。

下圖說明了這種關(guān)系:

我需要說明的***一點(diǎn)是如何區(qū)分本地和遠(yuǎn)程倉庫。通常(但不是每一次),本地倉庫是我們運(yùn)行g(shù)it的地方,遠(yuǎn)程倉庫是我們需要連接的。

所以在push中,我們從本地倉庫發(fā)送更改(的地方)到遠(yuǎn)程倉庫。在Fetch中,我們從遠(yuǎn)程倉庫抓取更改(的地方)到本地倉庫。在Clone中,我們將遠(yuǎn)程倉庫克隆到本地。

當(dāng)git運(yùn)行helper時(shí),git將環(huán)境變量GIT_DIR設(shè)置為本地倉庫的Git目錄(比如:local/.git)。

項(xiàng)目開搞

在這篇文章中,我假設(shè)Go語言已經(jīng)被安裝,并且使用了環(huán)境變量$GOPATH指向一個(gè)為go的目錄。

讓我們以創(chuàng)建目錄go/src/git-remote-go開始。這樣的話我們就可以通過運(yùn)行g(shù)o install來安裝我們的插件(假設(shè)go/bin在PATH中)。

在意識(shí)里面有了這一點(diǎn)后,我們可以編寫go/src/git-remote-go/main.go最初的幾行代碼。

 

  1. package mainimport ( 
  2. "log" 
  3. "os")func Main() (er error) { 
  4. if len(os.Args) < 3 { 
  5. return fmt.Errorf("Usage: git-remote-go remote-name url"
  6.  
  7. remoteName := os.Args[1
  8. url := os.Args[2]}func main() { 
  9. if err := Main(); err != nil { 
  10. log.Fatal(err) 
  11. }} 

我將Main()分割了開來,因?yàn)楫?dāng)我們需要返回錯(cuò)誤時(shí)錯(cuò)誤處理將會(huì)變得更容易。這里我們也可以使用defet,因?yàn)閘og.Fatal調(diào)用了os.Exit但不調(diào)用defer里面的函數(shù)。

現(xiàn)在,讓我們看下git-remote-testgit文件的最頂部,看下接下來需要做什么。

  1. #!/bin/sh# Copyright (c) 2012 Felipe Contrerasalias=$1url=$2dir="$GIT_DIR/testgit 
  2. /$alias"prefix="refs/testgit/$alias"default_refspec="refs/heads/*:${prefix}/heads 
  3. /*"refspec="${GIT_REMOTE_TESTGIT_REFSPEC-$default_refspec}"test -z "$refspec" && prefix="refs"GIT_DIR="$url 
  4. /.git"export GIT_DIRforce=mkdir -p "$dir"if test -z "$GIT_REMOTE_TESTGIT_NO_MARKS"then gitmarks="$dir/git.marks" 
  5. testgitmarks="$dir/testgit.marks" 
  6. test -e "$gitmarks" || >"$gitmarks" 
  7. test -e "$testgitmarks" || >"$testgitmarks"fi 

他們稱之為alias的變量就是我們所說的remoteName。url則是同樣的意義。

下一個(gè)聲明是:

dir="$GIT_DIR/testgit/$alias"

這里在Git目錄下創(chuàng)建了一個(gè)命名空間以標(biāo)識(shí)testgit協(xié)議和我們正在使用的遠(yuǎn)程路徑。通過這樣,testgit下面origin分支下的文件就能與backup分支下面的文件區(qū)分開來。

再下面,我們看到這樣的聲明:

mkdir -p "$dir"

此處確保了本地目錄已被創(chuàng)建,如果不存在則創(chuàng)建。

讓我們?yōu)槲覀兊腉o程序添加本地目錄的創(chuàng)建。

  1. // Add "path" to the import 
  2. listlocaldir := path.Join(os.Getenv("GIT_DIR"), "go", remoteName) 
  3. if err := os.MkdirAll(localdir, 0755); 
  4. err != nil { 
  5. return err 

緊接著上面的腳本,我們有以下幾行:

prefix="refs/testgit/$alias"default_refspec="refs/heads/*:${prefix}/heads
/*"refspec="${GIT_REMOTE_TESTGIT_REFSPEC-$default_refspec}"test -z "$refspec" && prefix="refs"

這里快速談?wù)撘幌聄efs。

在git中,refs存放在.git/refs:

.git

 

  1. └── refs 
  2.  
  3. ├── heads 
  4.  
  5. │ └── master 
  6.  
  7. ├── remotes 
  8.  
  9. │ ├── gravy 
  10.  
  11. │ └── origin 
  12.  
  13. │ └── master 
  14.  
  15. └── tags 

在上面的樹中,remotes/origin/master包括了遠(yuǎn)程origin中mater分支下最近大量的提交。而heads/master則關(guān)聯(lián)你本地mater分支下最近大量的提交。一個(gè)ref就像一個(gè)指向一次提交的指針。

refspec則可以讓我把遠(yuǎn)程的refs的本地的refs映射起來。在上面的代碼中,prefix就是會(huì)被遠(yuǎn)程refs保留的目錄。如果遠(yuǎn)程的名 稱是原始的,那么遠(yuǎn)程master分支將會(huì)由.git/refs/testgit/origin/master所指定。這樣就很基本地為遠(yuǎn)程的分支創(chuàng)建了 指定協(xié)議的命名空間。

接下來的這一行則是refspec。這一行

default_refspec="refs/heads/*:${prefix}/heads/*"

可以擴(kuò)展成

default_refspec="refs/heads/*:refs/testgit/$alias/*"

這意味著遠(yuǎn)程分支的映射看起來就像把refs/heads/*(這里的*表示任意文本)對(duì)應(yīng)到refs/testgit/$alias/*(這里 的*將會(huì)被前面的*表示的文本替換)。例如,refs/heads/master將會(huì)映射到refs/testgit/origin/master。

基本上來講,refspec允許testgit添加一個(gè)新的分支到自己的樹中,例如這樣:

.git

  1. └── refs 
  2.  
  3. ├── heads 
  4.  
  5. │ └── master 
  6.  
  7. ├── remotes 
  8.  
  9. │ └── origin 
  10.  
  11. │ └── master 
  12.  
  13. ├── testgit 
  14.  
  15. │ └── origin 
  16.  
  17. │ └── master 
  18.  
  19. └── tags 

下一行

refspec="${GIT_REMOTE_TESTGIT_REFSPEC-$default_refspec}"

把$refspec設(shè)置成$GIT_REMOTE_TESTGIT_REFSPEC,除非它不存在,否則它會(huì)成 為$default_refspec。這樣的話就能通過testgit測(cè)試其他的refspecs了。我們假設(shè)都已經(jīng)成功設(shè)置 了$default_refspec。

***,再下一行,

test -z "$refspec" && prefix="refs"

按照我們的理解,看起來像是如果$GIT_REMOTE_TESTGIT_REFSPEC存在卻為空時(shí)則把$prefix設(shè)置成refs。

我們需要自己的refspec,所以需要添加這一行

refspec := fmt.Sprintf("refs/heads/*:refs/go/%s/*", remoteName)

緊隨上面的代碼,我們看到了

GIT_DIR="$url/.git"export GIT_DIR

關(guān)于$GIT_DIR的另一個(gè)事實(shí)就是如果它有在環(huán)境變量中設(shè)置,那么底層的git將會(huì)使用環(huán)境變量中$GIT_DIR的目錄作為它的.git目錄,而不再是本地目錄的.git。這個(gè)命令使得未來全部插件的Git命令都能在遠(yuǎn)程制品庫的上下文中執(zhí)行。

我們把這點(diǎn)轉(zhuǎn)換成

  1. if err := os.Setenv("GIT_DIR", path.Join(url, ".git")); err != nil { 
  2. return err} 

當(dāng)然請(qǐng)記住,那個(gè)$dir和我們變量中的localdir依然指向我們正在fetch或push的子目錄。

main塊里面還有一小段代碼

  1. if test -z "$GIT_REMOTE_TESTGIT_NO_MARKS"then gitmarks="$dir/git.marks" 
  2. testgitmarks="$dir/testgit.marks" 
  3. test -e "$gitmarks" || >"$gitmarks" 
  4. test -e "$testgitmarks" || >"$testgitmarks"fi 

按我們的理解是,如果$GIT_REMOTE_TESTGIT_NO_MARKS未設(shè)置,if語句中的內(nèi)容將會(huì)被執(zhí)行。

這些標(biāo)識(shí)文件可以紀(jì)錄像git fast-export和git fast-import這些傳遞過程中ref和blob的有關(guān)信息。有一點(diǎn)是非常重要的,即這些標(biāo)識(shí)在各式各樣的插件中都是一樣的,所以他們都是保存在localdir中。

這里,$gitmarks關(guān)聯(lián)著我們本地制品庫中g(shù)it寫入的標(biāo)識(shí),$testgitmarks則保存遠(yuǎn)程處理寫入的標(biāo)識(shí)。

下面這兩行有點(diǎn)像touch的使用,如果標(biāo)識(shí)文件不存在,則創(chuàng)建一個(gè)空的。

test -e "$gitmarks" || >"$gitmarks"test -e "$testgitmarks" || >"$testgitmarks"

我們自己的程序中需要這些文件,所以讓我們以編寫一個(gè)Touch函數(shù)開始。

 

  1. // Create path as an empty file if it doesn't exist, otherwise do nothing.// This works by opening a file in exclusive mode; if it already exists,// an error will be returned rather than truncating it.func Touch(path string) error { 
  2. file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666
  3. if os.IsExist(err) { 
  4. return nil 
  5. else if err != nil { 
  6. return err 
  7.  
  8. return file.Close()} 

現(xiàn)在我們可以創(chuàng)建標(biāo)識(shí)文件了。

  1. gitmarks := path.Join(localdir, "git.marks")gomarks := path.Join(localdir, "go.marks")if err := Touch(gitmarks); err != nil { 
  2. return err}if err := Touch(gomarks); err != nil { 
  3. return err} 

然后,我遇到的一個(gè)問題就是,如果因?yàn)槟承┰蚨鴮?dǎo)致插件失敗的話,這些標(biāo)識(shí)文件將會(huì)處于殘留在一個(gè)無效的狀態(tài)。為了預(yù)防這一點(diǎn),我們可以先保存文件的原始內(nèi)容,并且如果Main()函數(shù)返回一個(gè)錯(cuò)誤的話我們就重寫他們。

  1. // add "io/ioutil" to importsoriginalGitmarks, err := ioutil.ReadFile(gitmarks)if err != nil { 
  2. return err}originalGomarks, err := ioutil.ReadFile(gomarks)if err != nil { 
  3. return err}defer func() { 
  4. if er != nil { 
  5. ioutil.WriteFile(gitmarks, originalGitmarks, 0666
  6. ioutil.WriteFile(gomarks, originalGomarks, 0666
  7. }}() 

***我們可以從關(guān)鍵命令操作開始。

命令行通過標(biāo)準(zhǔn)輸入流stdin傳遞到插件,也就是每一條命令是以回車結(jié)尾和一個(gè)字符串。插件則通過標(biāo)準(zhǔn)輸出流stdout對(duì)命令作出響應(yīng);標(biāo)準(zhǔn)錯(cuò)誤流stderr則通過管道輸出給終端用戶。

下面來編寫我們自己的命令操作。

 

  1. // Add "bufio" to import list.stdinReader := bufio.NewReader(os.Stdin)for { 
  2. // Note that command will include the trailing newline. 
  3. command, err := stdinReader.ReadString('\n'
  4. if err != nil { 
  5. return err 
  6.  
  7. switch { 
  8. case command == "capabilities\n"
  9. // ... 
  10. case command == "\n"
  11. return nil 
  12. default
  13. return fmt.Errorf("Received unknown command %q", command) 
  14. }} 

capabilities 命令

***條有待實(shí)現(xiàn)的命令是capabilities。插件要求能以空行結(jié)尾并以行分割的形式輸出顯示它能提供的命令和它所支持的操作。

  1. echo 'import'echo 'export'test -n "$refspec" && echo "refspec $refspec"if test -n "$gitmarks"then echo "*import-marks $gitmarks" 
  2. echo "*export-marks $gitmarks"fitest -n "$GIT_REMOTE_TESTGIT_SIGNED_TAGS" && echo "signed-tags"test -n "$GIT_REMOTE_TESTGIT_NO_PRIVATE_UPDATE" && echo "no-private-update"echo 'option'echo 

上面使用列表中聲明了此插件支持import,import和option命令操作。option命令允許git改變我們的插件中冗長的部分。

signed-tags意味著當(dāng)git為export命令創(chuàng)建了一個(gè)快速導(dǎo)入的流時(shí),它將會(huì)把–signed-tags=verbatim傳遞給git-fast-export。

no-private-update則指示著git不需要更新私有的ref當(dāng)它被成功push后。我未曾看到有需要用到這個(gè)特性。

refspec $refspec用于告訴git我們需要使用哪個(gè)refspec。

*import-marks $gitmarks和*export-marks $gitmarks意思是git應(yīng)該保存它生成的標(biāo)識(shí)到gitmarks文件中。*號(hào)表示如果git不能識(shí)別這幾行,它必須失敗返回而不是忽略他們。這是 因?yàn)椴寮蕾囉谒4娴臉?biāo)識(shí)文件,并且不能和git不支持的版本一起工作。

我們先忽略signed-tags,no-private-update和option,因?yàn)樗鼈冇糜谠趃it-remote-testgit未完成的測(cè)試,并且在我們這個(gè)例子中也不需要這些。我們可以這樣簡(jiǎn)單地實(shí)現(xiàn)上面這些,如:

  1. case command == "capabilities\n"
  2. fmt.Printf("import\n"
  3. fmt.Printf("export\n"
  4. fmt.Printf("refspec %s\n", refspec) 
  5. fmt.Printf("*import-marks %s\n", gitmarks) 
  6. fmt.Printf("*export-marks %s\n", gitmarks) 
  7. fmt.Printf("\n"

list命令

下一個(gè)命令是list。這個(gè)命令的使用說明并沒有包括在capabilities命令輸出的使用說明列表中,是因?yàn)樗ǔ6际遣寮仨氈С值摹?/p>

當(dāng)插件接收到一個(gè)list命令時(shí),它應(yīng)該打印輸出遠(yuǎn)程制品庫上的ref,并每行以$objectname $refname這樣的格式用一系列的行來表示,并且***跟著一行空行。$refname對(duì)應(yīng)著ref的名稱,$objectname則是ref指向的內(nèi) 容。$objectname可以是一次提交的哈希,或者用@$refname表示指向另外一個(gè)ref,或者是用?表示ref的值不可獲得。

git-remote-testgit的實(shí)現(xiàn)如下。

git for-each-ref --format='? %(refname)' 'refs/heads/'head=$(git symbolic-ref HEAD)echo "@$head HEAD"echo

記住,$GIT_DIR將觸發(fā)git for-each-ref在遠(yuǎn)程制品庫的執(zhí)行,并將會(huì)為每一個(gè)分支打印一行? $refname,同時(shí)還有@$head HEAD,這里的$head即為指向制品庫HEAD的ref的名稱。

在一個(gè)常規(guī)的制品庫里一般會(huì)有兩個(gè)分支,即master主分支和dev開發(fā)分支,這樣的話上面的輸出可能就像這樣

  1. ? refs/heads/master 
  2. ? refs/heads/development 
  3. @refs/heads/master HEAD 
  4. <blank> 

現(xiàn)在讓我們自己來寫這些。先寫一個(gè)GitListRefs()函數(shù),因?yàn)槲覀兩院驎?huì)再次用到。

 

  1. // Add "os/exec" and "bytes" to the import list.// Returns a map of refnames to objectnames.func GitListRefs() (map[string]string, error) { 
  2. out, err := exec.Command( 
  3. "git""for-each-ref""--format=%(objectname) %(refname)"
  4. "refs/heads/"
  5. ).Output() 
  6. if err != nil { 
  7. return nil, err 
  8.  
  9. lines := bytes.Split(out, []byte{'\n'}) 
  10. refs := make(map[string]string, len(lines)) 
  11. for _, line := range lines { 
  12. fields := bytes.Split(line, []byte{' '}) 
  13.  
  14. if len(fields) < 2 { 
  15. break 
  16.  
  17. refs[string(fields[1])] = string(fields[0]) 
  18.  
  19. return refs, nil} 
  20.  
  21. 現(xiàn)在編寫GitSymbolicRef()。 
  22.  
  23. func GitSymbolicRef(name string) (string, error) { 
  24. out, err := exec.Command("git""symbolic-ref", name).Output() 
  25. if err != nil { 
  26. return "", fmt.Errorf( 
  27. "GitSymbolicRef: git symbolic-ref %s: %v", name, out, err) 
  28.  
  29. return string(bytes.TrimSpace(out)), nil} 

然后可以像這樣來實(shí)現(xiàn)list命令。

 

  1. case command == "list\n"
  2. refs, err := GitListRefs() 
  3. if err != nil { 
  4. return fmt.Errorf("command list: %v", err) 
  5.  
  6. head, err := GitSymbolicRef("HEAD"
  7. if err != nil { 
  8. return fmt.Errorf("command list: %v", err) 
  9.  
  10. for refname := range refs { 
  11. fmt.Printf("? %s\n", refname) 
  12.  
  13. fmt.Printf("@%s HEAD\n", head) 
  14. fmt.Printf("\n"

import 命令

下一步是git在fetch或clone時(shí)會(huì)用到的import命令。這個(gè)命令實(shí)際來源于batch:它把import $refname作為一系列的行并用一個(gè)空行結(jié)束來發(fā)送。當(dāng)git將此命令發(fā)送到輔助插件時(shí),它將以二進(jìn)制形式執(zhí)行g(shù)it fast-import,并且通過管道將標(biāo)準(zhǔn)輸出stdout和標(biāo)準(zhǔn)輸入stdin綁定起來。換句話說,輔助插件期望能在標(biāo)準(zhǔn)輸出stdout上返回一個(gè) git fast-export流。

讓我們看下git-remote-testgit的實(shí)現(xiàn)。

  1. # read all import lineswhile truedo ref="${line#* }" 
  2. refs="$refs $ref" 
  3. read line test "${line%% *}" != "import" && breakdoneif test -n "$gitmarks"then echo "feature import-marks=$gitmarks" 
  4. echo "feature export-marks=$gitmarks"fiif test -n "$GIT_REMOTE_TESTGIT_FAILURE"then echo "feature done" 
  5. exit 1fiecho "feature done"git fast-export \ 
  6. ${testgitmarks:+"--import-marks=$testgitmarks"} \ 
  7. ${testgitmarks:+"--export-marks=$testgitmarks"} \ 
  8. $refs | 
  9. sed -e "s#refs/heads/#${prefix}/heads/#g"echo "done" 

最頂部的循環(huán),正如注釋所說的,將全部的import $refname命令匯總到一個(gè)單一的變量$refs中,而$refs則是以空格分隔的列表。

接下來的,如果腳本正在使用gitmarks文件(假設(shè)是這樣),將會(huì)輸出feature import-marks=$gitmarks和feature export-marks=$gitmarks。這里告訴git需要把–import-marks=$gitmarks和–export- marks=$gitmarks傳遞給git fast-import。

再下一行中,如果出于測(cè)試目的設(shè)置了$GIT_REMOTE_TESTGIT_FAILURE,插件將會(huì)失敗。

在那以后,feature done將會(huì)輸出,暗示著將緊跟輸出導(dǎo)出的流內(nèi)容。

***,git fast-export在遠(yuǎn)程制品庫被調(diào)用,在遠(yuǎn)程標(biāo)識(shí)上設(shè)置指定的標(biāo)識(shí)文件以及$testgitmarks,然后返回我們需要導(dǎo)出的ref列表。

git-fast-export命令的輸出內(nèi)容,通過管道經(jīng)過將refs/heads/匹配到refs/testgit/$alias/heads/的sed命令。因此在export導(dǎo)出時(shí),我們傳遞給git的refspec將能很好的使用這個(gè)匹配映射。

在導(dǎo)出流后面,緊跟done輸出。

我們可以用go來嘗試一下。

 

  1. case strings.HasPrefix(command, "import "): 
  2. refs := make([]string, 0
  3.  
  4. for { 
  5. // Have to make sure to trim the trailing newline. 
  6. ref := strings.TrimSpace(strings.TrimPrefix(command, "import ")) 
  7.  
  8. refs = append(refs, ref) 
  9. command, err = stdinReader.ReadString('\n'
  10. if err != nil { 
  11. return err 
  12.  
  13. if !strings.HasPrefix(command, "import ") { 
  14. break 
  15.  
  16. fmt.Printf("feature import-marks=%s\n", gitmarks) 
  17. fmt.Printf("feature export-marks=%s\n", gitmarks) 
  18. fmt.Printf("feature done\n"
  19.  
  20. args := []string{ 
  21. "fast-export"
  22. "--import-marks", gomarks, 
  23. "--export-marks", gomarks, 
  24. "--refspec", refspec} 
  25. args = append(args, refs...) 
  26.  
  27. cmd := exec.Command("git", args...) 
  28. cmd.Stderr = os.Stderr 
  29. cmd.Stdout = os.Stdout 
  30. if err := cmd.Run(); err != nil { 
  31. return fmt.Errorf("command import: git fast-export: %v", err) 
  32.  
  33. fmt.Printf("done\n"

export命令

下一步是export命令。當(dāng)我們完成了這個(gè)命令,我們的輔助插件也就大功告成了。

當(dāng)我們對(duì)遠(yuǎn)程倉庫進(jìn)行push時(shí),Git 發(fā)布了這個(gè)export命令。通過標(biāo)準(zhǔn)輸入stdin發(fā)送這個(gè)命令后,git將通過由git fast-export提供的流來追蹤,而與git fast-export對(duì)應(yīng)的是可以向遠(yuǎn)程倉庫操縱的git fast-import命令。

  1. if test -n "$GIT_REMOTE_TESTGIT_FAILURE"then 
  2. # consume input so fast-export doesn't get SIGPIPE; 
  3. # git would also notice that case, but we want 
  4. # to make sure we are exercising the later 
  5. # error checks 
  6. while read line; do test "done" = "$line" && break done exit 1fibefore=$(git for-each-ref --format=' %(refname) %(objectname) ')git fast-import \ 
  7. ${force:+--force} \ 
  8. ${testgitmarks:+"--import-marks=$testgitmarks"} \ 
  9. ${testgitmarks:+"--export-marks=$testgitmarks"} \ 
  10. --quiet# figure out which refs were updatedgit for-each-ref --format='%(refname) %(objectname)' |while read ref ado case "$before" in 
  11. *" $ref $a "*) 
  12. continue ;; # unchanged 
  13. esac if test -z "$GIT_REMOTE_TESTGIT_PUSH_ERROR" 
  14. then echo "ok $ref" 
  15. else echo "error $ref $GIT_REMOTE_TESTGIT_PUSH_ERROR" 
  16. fidoneecho 

***行的if語句,和前面的一樣,僅僅是為了測(cè)試的目的而已。

再下一行更有意思。它創(chuàng)建了一個(gè)以空格分割的列表,且這個(gè)列表是以$refname $objectname對(duì) 來表示我們決定哪些將要在import中被更新ref。

再接下來的命令則相當(dāng)具有解釋性。git fast-import工作于我們接收到的標(biāo)準(zhǔn)輸入流,–forece參數(shù)表示是否特定,–quiet,以及遠(yuǎn)程的marks標(biāo)記文件。

在這之下再次運(yùn)行了git for-each-ref來檢測(cè)refs有什么變化。對(duì)于這個(gè)命令返回的每一個(gè)ref,都會(huì)檢測(cè)$refname $objectname對(duì)是否出現(xiàn)在$before列表里面。如果是,說明沒什么變化并且繼續(xù)進(jìn)行下一步。然而如果ref不存這個(gè)$before列表中, 將會(huì)打包輸出ok $refname以告知git對(duì)應(yīng)的ref被成功更新了。如果打印error $refname $message則是通知git對(duì)應(yīng)的ref在遠(yuǎn)程終端導(dǎo)入失敗。

***,打印的一個(gè)空行表明導(dǎo)入完畢。

現(xiàn)在我們可以自己編寫這些代碼了。我們可以使用我們之前定義的GitListRefs()方法。

 

  1. case command == "export\n"
  2. beforeRefs, err := GitListRefs() 
  3. if err != nil { 
  4. return fmt.Errorf("command export: collecting before refs: %v", err) 
  5.  
  6. cmd := exec.Command("git""fast-import""--quiet"
  7. "--import-marks="+gomarks, 
  8. "--export-marks="+gomarks) 
  9.  
  10. cmd.Stderr = os.Stderr 
  11. cmd.Stdin = os.Stdin 
  12.  
  13. if err := cmd.Run(); err != nil { 
  14. return fmt.Errorf("command export: git fast-import: %v", err) 
  15.  
  16. afterRefs, err := GitListRefs() 
  17. if err != nil { 
  18. return fmt.Errorf("command export: collecting after refs: %v", err) 
  19.  
  20. for refname, objectname := range afterRefs { 
  21. if beforeRefs[refname] != objectname { 
  22. fmt.Printf("ok %s\n", refname) 
  23.  
  24. fmt.Printf("\n"

小牛試刀

執(zhí)行 go install,應(yīng)該能夠構(gòu)建和安裝 git-remote-go 到 go/bin。

你可以這樣來測(cè)試驗(yàn)證:首先創(chuàng)建兩個(gè)空的git倉庫,然后在testlocal中commit一個(gè)提交,并通過我們新的輔助插件helper把它push到testremote。

  1. $ cd $HOME 
  2. $ git init testremote 
  3. Initialized empty Git repository in $HOME/testremote/.git/ 
  4. $ git init testlocal 
  5. Initialized empty Git repository in $HOME/testlocal/.git/ 
  6. $ cd testlocal 
  7. $ echo 'Hello, world!' >hello.txt 
  8. $ git add hello.txt 
  9. $ git commit -m "First commit." 
  10. [master (root-commit) 50d3a83] First commit. 
  11. 1 file changed, 1 insertion(+) 
  12. create mode 100644 hello.txt 
  13. $ git remote add origin go::$HOME/testremote 
  14. $ git push --all origin 
  15. To go::$HOME/testremote 
  16. * [new branch] master -> master 
  17. $ cd ../testremote 
  18. $ git checkout master 
  19. $ ls 
  20. hello.txt 
  21. $ cat hello.txt 
  22. Hello, world! 

git 遠(yuǎn)程輔助插件的使用

實(shí)現(xiàn)接口后,Git 遠(yuǎn)程輔助插件可以用于其他的源控制(如 felipec/git-remote-hg),或者推送代碼到 CouchDBs (peritus/git-remote-couch), 等等其他。你也可以想象更多其他可能的用處。

出于我最初的動(dòng)機(jī),我寫了一個(gè)git遠(yuǎn)程輔助插件git-remote-grave。你可以使用它來push和fetch你文件系統(tǒng)上或者經(jīng)過HTTP/HTTPS協(xié)議的加密檔案文檔。

$ git remote add usb grave::/media/usb/backup.grave
$ git push --all backup

使用兩種壓縮技巧,可以讓檔案文檔的大小通??s小為原來的22%。

如果你想要一個(gè)便利的地方去存放你加密后的git倉庫,可以訪問我創(chuàng)建的這個(gè)站點(diǎn): filegrave.com 。

此文章的討論交流部分放置在 Hacker News 和 /r/programming。

責(zé)任編輯:王雪燕 來源: oschina
相關(guān)推薦

2016-01-06 09:57:23

編寫PHP擴(kuò)展

2020-06-02 10:04:58

IT部門首席信息官CIO

2024-04-16 08:09:36

JavapulsarAPI

2023-12-12 08:08:17

插件PRPulsar

2013-01-25 09:53:40

GitHub

2022-03-21 08:49:01

存儲(chǔ)引擎LotusDB

2013-10-09 10:04:20

LinuxGit

2025-02-13 07:00:00

Dubbo-goJava服務(wù)端

2017-01-12 22:36:30

2024-08-12 08:33:05

2020-01-11 17:00:07

DjangoPythonWeb API

2021-07-06 14:36:05

RustLinux內(nèi)核模塊

2021-08-23 11:35:37

代碼開發(fā)開源

2016-08-05 12:58:44

GitLinux開源

2021-02-05 16:03:48

JavaScript游戲?qū)W習(xí)前端

2010-03-31 17:21:04

云計(jì)算

2015-04-30 08:03:36

2024-08-14 13:24:24

2021-01-01 19:30:21

Python編程語言

2009-04-03 15:21:37

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)