随手笔记pro git



// Git 介绍
Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。
每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。
为提高性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照作一链接


在保存到 Git 之前,所有数据都要进行内容的校验和(checksum)计算,并将此结果作为数据的唯一标识和索引
Git 使用 SHA-1 算法计算数据的校验和,通过对文件的内容或目录的结构计算出一个 SHA-1 哈希值,作为指纹字符串。
该字串由 40 个十六进制字符(0-9 及 a-f)组成


 Git 内都只有三种状态:已提交(committed),已修改(modified)和已暂存(staged)。已提交表示该文件已经被安全地保存在本地数据库中了;
 已修改表示修改了某个文件,但还没有提交保存;已暂存表示把已修改的文件放在下次提交时要保存的清单中


每个项目都有一个 Git 目录(译注:如果 git clone 出来的话,就是其中 .git 的目录;如果 git clone --bare 的话,
新建的目录本身就是 Git 目录。虽然名字不叫.git,但是里面的内容和.git一样),它是 Git 用来保存元数据和对象数据库的地方。该目录非常重要,
每次克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据。


// Git配置


/etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。
~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
当前项目的 git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。
每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。


第一个要配置的是你个人的用户名称和电子邮件地址。这两条配置很重要,每次 Git 提交时都会引用这两条信息,说明是谁提交了更新,
所以会随更新内容一起被永久纳入历史记录:


$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
如果用了 --global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。
如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 --global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。


要检查已有的配置信息,可以使用 git config --list 命令:
这个命令会把 ~/.gitconfig + 当前项目下的.git/config 一起显示出来


git config user.name //单独显示用户名
git help config //查看命令帮助


// 三种方式查看
git help <verb>
$ git <verb> --help
$ man git-<verb>




//管理Git项目
要对现有的某个项目开始用 Git 管理,只需到此项目所在的目录,执行:
$ git init


Git 仓库复制一份出来,这就需要用到 git clone 命令
git clone git://github.com/schacon/grit.git
如果希望在克隆的时候,自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字:
git clone git://github.com/schacon/grit.git mygrit


确定哪些文件当前处于什么状态,可以用 git status 命令


//忽略文件
忽略某些文件: (可以手动在当前项目中创建)
.gitignore 的格式规范如下:


所有空行或者以注释符号 # 开头的行都会被 Git 忽略。
可以使用标准的 glob 模式匹配。
匹配模式最后跟反斜杠(/)说明要忽略的是目录。
要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。


# 此为注释 – 将被 Git 忽略
    # 忽略所有 .a 结尾的文件
    *.a
    # 但 lib.a 除外
    !lib.a
    # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
    /TODO
    # 忽略 build/ 目录下的所有文件
    build/
    # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
    doc/*.txt
    
// 查看比较
要查看具体修改了什么地方,可以用 git diff 命令
命令比较的是工作目录中当前文件和暂存区域快照之间的差异
若要看已经暂存起来的文件和上次提交时的快照之间的差异,可以用 git diff --cached 命令。(
Git 1.6.1 及更高版本还允许使用 git diff --staged,效果是相同的,但更好记些。)来看看实际的效果
    
    git config --global core.editor 命令设定你喜欢的编辑软件
    
// 提交更新    
git commit -m "xxxx"
提交时记录的是放在暂存区域的快照,任何还未暂存的仍然保持已修改状态
只要在提交的时候,给 git commit 加上 -a 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤
其实是中间做了git add 处理


//移除文件
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。
可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
最后提交的时候,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f
(译注:即 force 的首字母),以防误删除文件后丢失修改的内容。
    
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,
仅是从跟踪清单中删除。比如一些大型日志文件或者一堆 .a 编译文件,不小心纳入仓库后,要移除跟踪但不删除文件,
以便稍后在 .gitignore 文件中补上,用 --cached 选项即可:


$ git rm --cached readme.txt    
    
// 移动文件


要在 Git 中对文件改名,可以这么做:
$ git mv file_from file_to 
运行 git mv 就相当于运行了下面三条命令:


$ mv README.txt README
    $ git rm README.txt
    $ git add README
   
关于push提交远程仓库,如果远程仓库是比现在要提交的要新,提交会失败
   


   
   
// 查看提交历史
回顾下提交历史,可以使用 git log 命令查看
默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面
我们常用 -p 选项展开显示每次提交的内容差异,用 -2 则仅显示最近的两次更新:
--stat,仅显示简要的增改行数统计:
--pretty 选项,可以指定使用完全不同于默认格式的方式展示提交历史。比如用 oneline 将每个提交放在一行显示,
这在提交数很大时非常有用。另外还有 short,full 和 fuller 可以用,展示的信息或多或少有些不同
git log
git log -p -2
git log --stat
git log --pretty=oneline
git log --pretty=format:"%h - %an, %ar : %s"


format 选项 说明
    %H 提交对象(commit)的完整哈希字串
    %h 提交对象的简短哈希字串
    %T 树对象(tree)的完整哈希字串
    %t 树对象的简短哈希字串
    %P 父对象(parent)的完整哈希字串
    %p 父对象的简短哈希字串
    %an 作者(author)的名字
    %ae 作者的电子邮件地址
    %ad 作者修订日期(可以用 -date= 选项定制格式)
    %ar 作者修订日期,按多久以前的方式显示
    %cn 提交者(committer)的名字
    %ce 提交者的电子邮件地址
    %cd 提交日期
    %cr 提交日期,按多久以前的方式显示
    %s 提交说明


用 oneline 或 format 时结合 --graph 选项,可以看到开头多出一些 ASCII 字符串表示的简单图形,
形象地展示了每个提交所在的分支及其分化衍合情况


git log 命令支持的选项
选项 说明
    -p 按补丁格式显示每个更新之间的差异。
    --stat 显示每次更新的文件修改统计信息。
    --shortstat 只显示 --stat 中最后的行数修改添加移除统计。
    --name-only 仅在提交信息后显示已修改的文件清单。
    --name-status 显示新增、修改、删除的文件清单。
    --abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。
    --relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)。
    --graph 显示 ASCII 图形表示的分支合并历史。
    --pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。


  -(n) 仅显示最近的 n 条提交
    --since, --after 仅显示指定时间之后的提交。
    --until, --before 仅显示指定时间之前的提交。
    --author 仅显示指定作者相关的提交。
    --committer 仅显示指定提交者相关的提交。


还有按照时间作限制的选项,比如 --since 和 --until。下面的命令列出所有最近两周内的提交:
$ git log --since=2.weeks


你可以给出各种时间格式,比如说具体的某一天(“2008-01-15”),或者是多久以前(“2 years 1 day 3 minutes ago”)。


还可以给出若干搜索条件,列出符合的提交。用 --author 选项显示指定作者的提交,用 --grep 选项搜索提交说明中的关键字。
(请注意,如果要得到同时满足这两个选项搜索条件的提交,就必须用 --all-match 选项。否则,满足任意一个条件的提交都会被匹配出来)
git log选项是路径(path),如果只关心某些文件或者目录的历史提交,可以在 git log 选项的最后指定它们的路径。
因为是放在最后位置上的选项,所以用两个短划线(--)隔开之前的选项和后面限定的路径名


git log --pretty="%h - %s" --author=gitster --since="2008-10-01" \
    --before="2008-11-01" --no-merges -- t/


// 修改最后一次提交
有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用 --amend 选项重新提交:
此命令将使用当前的暂存区域快照提交


如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行 --amend 提交:


$ git commit -m 'initial commit'
    $ git add forgotten_file
    $ git commit --amend
上面的三条命令最终只是产生一个提交,第二个提交命令修正了第一个的提交内容。




// 撤销操作
1. 取消已经暂存的文件
git reset HEAD filename


2. 取消对文件的修改
git checkout -- filename


***
如果改变了文件又add了,执行第二步没什么用,所以要首先取消暂存文件然后取消文件的修改
***


// 切换到某个历史节点上
git reset --hard commit_id


// 查看当前的远程库
要查看当前配置有哪些远程仓库,可以用 git remote 命令,它会列出每个远程库的简短名字。
在克隆完某个项目后,至少可以看到一个名为 origin 的远程库,Git 默认使用这个名字来标识你所克隆的原始仓库
$ git remote
    origin
可以加上 -v 选项(译注:此为 --verbose 的简写,取首字母),显示对应的克隆地址:
$ git remote -v
    origin git://github.com/schacon/ticgit.git


// 添加远程仓库
git remote add [shortname] [url]:
git remote add pb git://github.com/paulboone/ticgit.git


// 从远程仓库抓取数据
git fetch [remote-name]
此命令会到远程仓库中拉取所有你本地仓库中还没有的数据


可以使用 git pull 命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支。
在日常工作中我们经常这么用,既快且好


// 推送数据到远程仓库
git push [remote-name] [branch-name]
$ git push origin master

// 查看远程仓库信息
git remote show [remote-name] 查看某个远程仓库的详细信息
git remote show origin


// 远程仓库的删除和重命名
git remote rename 命令修改某个“远程仓库在本地“的简称,比如想把 pb 改成 paul,可以这么运行:


$ git remote rename pb paul
    $ git remote
    origin
    paul


碰到远端仓库服务器迁移,或者原来的克隆镜像不再使用,又或者某个参与者不再贡献代码,那么需要移除对应的远端仓库,
可以运行 git remote rm 命令:


$ git remote rm paul
    $ git remote
    origin


// 打标签
列出现有标签的命令非常简单,直接运行 git tag
git tag -l 'v1.4.2.*'


创建一个含附注类型的标签非常简单,用 -a (译注:取 annotated 的首字母)指定标签名字即可:
$ git tag -a v1.4 -m 'my version 1.4' commit id
而 -m 选项则指定了对应的标签说明,Git 会将此说明一同保存在标签对象中。如果没有给出该选项,Git 会启动文本编辑软件供你输入标签说明。


git show 命令查看相应标签的版本信息,并连同显示打标签时的提交对象。


如果你有自己的私钥,还可以用 GPG 来签署标签,只需要把之前的 -a 改为 -s (译注: 取 signed 的首字母)即可:
$ git tag -s v1.5 -m 'my signed 1.5 tag'


可以使用 git tag -v [tag-name] (译注:取 verify 的首字母)的方式验证已经签署的标签。
此命令会调用 GPG 来验证签名,所以你需要有签署者的公钥,存放在 keyring 中,才能验证:


// 轻量级标签 选项都不用


//分享标签
默认情况下,git push 并不会把标签传送到远端服务器上,只有通过显式命令才能分享标签到远端仓库。其命令格式如同推送分支,
运行 git push origin [tagname] 即可:
$ git push origin v1.5


如果要一次推送所有本地新增的标签上去,可以使用 --tags 选项:
git push origin --tags




// Git 命令别名
git config --global alias.co checkout
git config --global alias.last 'log -1 HEAD'


实际上 Git 只是简单地在命令中替换了你设置的别名。不过有时候我们希望运行某个外部命令,而非 Git 的子命令,
这个好办,只需要在命令前加上 ! 就行
 git config --global alias.visual '!gitk'
 


//分支
使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作
Git 鼓励在工作流程中频繁使用分支与合并 


由于 Git 中的分支实际上仅是一个包含所指对象校验和(40 个字符长度 SHA-1 字串)的文件,
所以创建和销毁一个分支就变得非常廉价。说白了,新建一个分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,当然也就很快了。


Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。
在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动


//创建一个新的分支
可以使用 git branch 命令:
$ git branch testing




Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为 HEAD 的特别指针。
在 Git 中,它是一个指向你正在工作中的本地分支的指针(译注:将 HEAD 想象为当前分支的别名。)
运行 git branch 命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去


//切换分支
执行 git checkout 命令。我们现在转换到新建的 testing 分支:
$ git checkout testing




$ git checkout -b iss53
-b 选项 当于执行下面这两条命令:
$ git branch iss53
    $ git checkout iss53


切换分支的时候最好保持一个清洁的工作区域


牢记:
Git 会把工作目录的内容恢复为检出某分支时它所指向的那个提交对象的快照。
它会自动添加、删除和修改文件以确保目录的内容和你当时提交时完全一样。




// 分支合并
git merge 命令来进行合并:
$ git checkout master
$ git merge hotfix



// 删除分支
使用 git branch 的 -d 选项执行删除操作:
$ git branch -d hotfix


// 遇到冲突时的分支合并
有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起
要看看哪些文件在合并时发生冲突,可以用 git status 查阅:
任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。


<<<<<<< HEAD:index.html
    <div id="footer">contact : email.support@github.com</div>
    =======
    <div id="footer">
    please contact us at support@github.com
    </div>
    >>>>>>> iss53:index.html
    
解决了所有文件里的所有冲突后,运行 git add 将把它们标记为已解决状态
    
// 分支管理
git branch 命令不仅仅能创建和删除分支,如果不加任何参数,它会给出当前所有分支的清单:    
    $ git branch
    iss53
    * master
    testing
 
注意看 master 分支前的 * 字符:它表示当前所在的分支。也就是说,如果现在提交更新,master 分支将随着开发进度前移。
若要查看各个分支最后一个提交对象的信息,运行 git branch -v:
$ git branch -v
    iss53 93b412c fix javascript issue
    * master 7a98805 Merge branch 'iss53'
    testing 782fd34 add scott to the author list in the readmes    
   
要从该清单中筛选出你已经(或尚未)与当前分支合并的分支,可以用 --merge 和 --no-merged 选项。
比如用 git branch --merge 查看哪些分支已被并入当前分支   
可以用 git branch --no-merged 查看尚未合并的工作:
 
还未合并进来的分支。由于这些分支中还包含着尚未合并进来的工作成果,所以简单地用 git branch -d 删除该分支会提示错误,
因为那样做会丢失数据: 不过,如果你确实想要删除该分支上的改动,可以用大写的删除选项 -D 强制执行


// 推送本地分支
git push origin serverfix




// 合并远程分支
如果要把该远程分支的内容合并到当前分支,可以运行 git merge origin/serverfix
如果想要一份自己的 serverfix 来开发,可以在远程分支的基础上分化出一个新的分支来:
$ git checkout -b serverfix origin/serverfix
这会切换到新建的 serverfix 本地分支,其内容同远程分支 origin/serverfix 一致,这样你就可以在里面继续开发了。


//跟踪远程分支
从远程分支 checkout 出来的本地分支,称为 跟踪分支 (tracking branch)。跟踪分支是一种和某个远程分支有直接联系的本地分支。
在跟踪分支里输入 git push,Git 会自行推断应该向哪个服务器的哪个分支推送数据。同样,在这些分支里运行 git pull 会获取所有远程索引,
并把它们的数据都合并到本地分支中来。


在克隆仓库时,Git 通常会自动创建一个名为 master 的分支来跟踪 origin/master。这正是 git push 和 git pull 一开始就能正常工作的原因


要为本地分支设定不同于远程分支的名字,只需在第一个版本的命令里换个名字:


$ git checkout -b sf origin/serverfix


// 删除远程分支
非常无厘头的语法来删除它:git push [远程名] :[分支名]
$ git push origin :serverfix
git push [远程名] [本地分支]:[远程分支] 语法,如果省略 [本地分支],那就等于是在说“在这里提取空白然后把它变成[远程分支]”。


// 分支的衍合
1.  merge 命令
它会把两个分支最新的快照(C3 和 C4)以及二者最新的共同祖先(C2)进行三方合并,合并的结果是产生一个新的提交对象(C5)


-->这个命令是先切到想要被合成的分支中,然后 merge 把想要合成的分支合进来,HEAD还是在当前分支上




2. rebase 命令
可以把在 C3 里产生的变化补丁在 C4 的基础上重新打一遍。在 Git 里,这种操作叫做衍合(rebase)。
有了 r


-->这个命令是先切换到要合成的分支中,然后 rebase到想要被合成的分支中,和merge是相反的
git rebase [主分支] [特性分支]


它的原理是回到两个分支最近的共同祖先,根据当前分支(也就是要进行衍合的分支 experiment)后续的历次提交对象
(这里只有一个 C3),生成一系列文件补丁,然后以基底分支(也就是主干分支 master)最后一个提交对象(C4)为新的出发点,
逐个应用之前准备好的补丁文件,最后会生成一个新的合并提交对象(C3'),从而改写 experiment 的提交历史,
使它成为 master 分支的直接下游


虽然最后整合得到的结果没有任何区别,但衍合能产生一个更为整洁的提交历史。如果视察一个衍合过的分支的历史记录,
看起来会更清楚:仿佛所有修改都是在一根线上先后进行的,尽管实际上它们原本是同时并行发生的。


一般我们使用衍合的目的,是想要得到一个能在远程分支上干净应用的补丁 — 比如某些项目你不是维护者,但想帮点忙的话,
最好用衍合:先在自己的一个分支里进行开发,当准备向主项目提交补丁的时候,根据最新的 origin/master 进行一次衍合操作然后再提交,
这样维护者就不需要做任何整合工作(译注:实际上是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决。),
只需根据你提供的仓库地址作一次快进合并,或者直接采纳你提交的补丁。


// 从一个特性分支里再分出一个特性分支的历史
$ git rebase --onto master server client
这好比在说:“取出 client 分支,找出 client 分支和 server 分支的共同祖先之后的变化,然后把它们在 master 上重演一遍”


《《《《 一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。 》》》》


//协议
Git 可以使用四种主要的协议来传输数据:本地传输,SSH 协议,Git 协议和 HTTP 协议


1. 本地协议
git clone /opt/git/project.git
git clone file:///opt/git/project.git
如果在 URL 开头明确使用 file:// ,那么 Git 会以一种略微不同的方式运行。如果你只给出路径,
Git 会尝试使用硬链接或直接复制它所需要的文件。如果使用了 file:// ,Git 会调用它平时通过网络来传输数据的工序,
而这种方式的效率相对较低。使用 file:// 前缀的主要原因是当你需要一个不包含无关引用或对象的干净仓库副本的时候 
添加一个本地仓库作为现有 Git 项目的远程仓库,可以这样做:
$ git remote add local_proj /opt/git/project.git


优点


基于文件仓库的优点在于它的简单,同时保留了现存文件的权限和网络访问权限。如果你的团队已经有一个全体共享的文件系统,
建立仓库就十分容易了。你只需把一份裸仓库的副本放在大家都能访问的地方,然后像对其他共享目录一样设置读写权限就可以了


缺点


这种方法的缺点是,与基本的网络连接访问相比,难以控制从不同位置来的访问权限。如果你想从家里的笔记本电脑上推送,
就要先挂载远程硬盘,这和基于网络连接的访问相比更加困难和缓慢




2. SSH 协议
SSH 也是唯一一个同时支持读写操作的网络协议。另外两个网络协议(HTTP 和 Git)通常都是只读的,所以虽然二者对大多数人都可用,
但执行写操作时还是需要 SSH。SSH 同时也是一个验证授权的网络协议;而因为其普遍性,一般架设和使用都很容易


通过 SSH 克隆一个 Git 仓库,你可以像下面这样给出 ssh:// 的 URL:
$ git clone ssh://user@server/project.git
或者不指明某个协议 — 这时 Git 会默认使用 SSH :
$ git clone user@server:project.git


优点
使用 SSH 的好处有很多。首先,如果你想拥有对网络仓库的写权限,基本上不可能不使用 SSH。其次,SSH 架设相对比较简单
 — SSH 守护进程很常见,很多网络管理员都有一些使用经验,而且很多操作系统都自带了它或者相关的管理工具。
 再次,通过 SSH 进行访问是安全的 — 所有数据传输都是加密和授权的。最后,和 Git 及本地协议一样,SSH 也很高效,
 会在传输之前尽可能压缩数据。


缺点


SSH 的限制在于你不能通过它实现仓库的匿名访问。即使仅为读取数据,人们也必须在能通过 SSH 访问主机的前提下才能访问仓库,
这使得 SSH 不利于开源的项目。如果你仅仅在公司网络里使用,SSH 可能是你唯一需要使用的协议。如果想允许对项目的匿名只读访问,
那么除了为自己推送而架设 SSH 协议之外,还需要支持其他协议以便他人访问读取。


3.Git 协议


Git 协议


接下来是 Git 协议。这是一个包含在 Git 软件包中的特殊守护进程; 它会监听一个提供类似于 SSH 服务的特定端口(9418),
而无需任何授权。打算支持 Git 协议的仓库,需要先创建 git-export-daemon-ok 文件 — 它是协议进程提供仓库服务的必要条件 — 
但除此之外该服务没有什么安全措施。要么所有人都能克隆 Git 仓库,要么谁也不能。这也意味着该协议通常不能用来进行推送。
你可以允许推送操作;然而由于没有授权机制,一旦允许该操作,网络上任何一个知道项目 URL 的人将都有推送权限。
不用说,这是十分罕见的情况。


优点


Git 协议是现存最快的传输协议。如果你在提供一个有很大访问量的公共项目,或者一个不需要对读操作进行授权的庞大项目,
架设一个 Git 守护进程来供应仓库是个不错的选择。它使用与 SSH 协议相同的数据传输机制,但省去了加密和授权的开销。


缺点


Git 协议消极的一面是缺少授权机制。用 Git 协议作为访问项目的唯一方法通常是不可取的。一般的做法是,
同时提供 SSH 接口,让几个开发者拥有推送(写)权限,其他人通过 git:// 拥有只读权限。Git 协议可能也是最难架设的协议。
它要求有单独的守护进程,需要定制 — 我们将在本章的 “Gitosis” 一节详细介绍它的架设 — 需要设定 xinetd 或类似的程序,
而这些工作就没那么轻松了。该协议还要求防火墙开放 9418 端口,而企业级防火墙一般不允许对这个非标准端口的访问。
大型企业级防火墙通常会封锁这个少见的端口。


4.HTTP/S 协议


最后还有 HTTP 协议。HTTP 或 HTTPS 协议的优美之处在于架设的简便性。基本上,只需要把 Git 的裸仓库文件放在 HTTP 的根目录下,
配置一个特定的 post-update 挂钩(hook)就可以搞定




// 指明一次提交
git show commid_id | branch_name


//想知道某个分支指向哪个特定的 SHA
git rev-parse branch_name


// 引用日志
git reflog 来查看引用日志:


如果你想查看仓库中 HEAD 在五次前的值,你可以使用引用日志的输出中的 @{n} 引用:
$ git show HEAD@{5}


你也可以使用这个语法来查看某个分支在一定时间前的位置。例如,想看你的 master 分支昨天在哪,你可以输入
$ git show master@{yesterday}


想要看类似于 git log 输出格式的引用日志信息,你可以运行 git log -g:
$ git log -g master


引用日志信息只存在于本地——这是一个记录你在你自己的仓库里做过什么的日志。
其他人拷贝的仓库里的引用日志不会和你的相同;而你新克隆一个仓库的时候,引用日志是空的,
因为你在仓库里还没有操作。git show HEAD@{2.months.ago} 这条命令只有在你克隆了一个项目至少两个月时才会有用——
如果你是五分钟前克隆的仓库,那么它将不会有结果返回。


***********************************************
// 祖先引用
如果你在引用最后加上一个 ^,Git 将其理解为此次提交的父提交
想看上一次提交,你可以使用 HEAD^,意思是“HEAD 的父提交”:
$ git show HEAD^


你也可以在 ^ 后添加一个数字——例如,d921970^2 意思是“d921970 的第二父提交”。这种语法只在合并提交时有用,
因为合并提交可能有多个父提交。第一父提交是你合并时所在分支,而第二父提交是你所合并的分支:


另外一个指明祖先提交的方法是 ~。这也是指向第一父提交,所以 HEAD~ 和 HEAD^ 是等价的。
当你指定数字的时候就明显不一样了。HEAD~2 是指“第一父提交的第一父提交”,也就是“祖父提交”——它会根据你指定的次数检索第一父提交
HEAD~3 也可以写成 HEAD^^^
你也可以混合使用这些语法——你可以通过 HEAD~3^2 指明先前引用的第二父提交(假设它是一个合并提交)。
***********************************************


// 交互式暂存


如果你运行git add时加上-i或者--interactive选项,Git就进入了一个交互式的shell模式,显示一些类似于下面的信息:


$ git add -i
    staged unstaged path
    1: unchanged +0/-1 TODO
    2: unchanged +1/-1 index.html
    3: unchanged +5/-1 lib/simplegit.rb


    *** Commands ***
    1: status 2: update 3: revert 4: add untracked
    5: patch 6: diff 7: quit 8: help
    What now>
    
// 暂存和撤回文件


如果你在What now>的提示后输入2或者u,这个脚本会提示你那些文件你想要暂存: 
输入相应的编号添加 旁边有*号,不想添加用 -编号移除
如果你在update>>提示后直接敲入回车,Git会替你把所有选中的内容暂存:


使用3或者r(代表revert,恢复)选项:


// 差异
要查看你暂存内容的差异,你可以使用6或者d(表示diff)命令。它会显示你暂存文件的列表,你可以选择其中的几个,显示其被暂存的差异。


//暂存补丁


只让Git暂存文件的某些部分而忽略其他也是有可能的。例如,你对simplegit.rb文件作了两处修改但是只想暂存其中一个而忽略另一个,
在Git中实现这一点非常容易。在交互式的提示符下,输入5或者p(表示patch,补丁)。Git会询问哪些文件你希望部分暂存;
然后对于被选中文件的每一节,他会逐个显示文件的差异区块并询问你是否希望暂存他们:


最后你也可以不通过交互式增加的模式来实现部分文件暂存——你可以在命令行下通过git add -p或者git add --patch来启动同样的脚本。


// 储藏(Stashing)
经常有这样的事情发生,当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,
而你想转到其他分支上进行一些工作。问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。
解决这个问题的办法就是git stash命令。


“‘储藏”“可以获取你工作目录的中间状态——也就是你修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,
随时可以重新应用。


现在你想切换分支,但是你还不想提交你正在进行中的工作;所以你储藏这些变更。为了往堆栈推送一个新的储藏,只要运行 git stash:
$ git stash


查看现有的储藏,你可以使用 git stash list:
$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051... Revert "added file_size"
stash@{2}: WIP on master: 21d80a5... added number to log


在这个案例中,之前已经进行了两次储藏,所以你可以访问到三个不同的储藏。你可以重新应用你刚刚实施的储藏,
所采用的命令就是之前在原始的 stash 命令的帮助输出里提示的:git stash apply。如果你想应用更早的储藏,
你可以通过名字指定它,像这样:git stash apply stash@{2}。如果你不指明,Git 默认使用最近的储藏并尝试应用它:


注意::
要恢复的时候要切换到恢复的分支上,不能再其他的分支上进行恢复,会有问题

对文件的变更被重新应用,但是被暂存的文件没有重新被暂存。想那样的话,你必须在运行 git stash apply 命令时带上一个 
--index 的选项来告诉命令重新应用被暂存的变更。如果你是这么做的,你应该已经回到你原来的位置:
$ git stash apply --index


apply 选项只尝试应用储藏的工作——储藏的内容仍然在栈上。要移除它,你可以运行 git stash drop,加上你希望移除的储藏的名字:
git stash drop stash@{0}


// 取消储藏(Un-applying a Stash)


在某些情况下,你可能想应用储藏的修改,在进行了一些其他的修改后,又要取消之前所应用储藏的修改。Git没有提供类似于 stash unapply 的命令,但是可以通过取消该储藏的补丁达到同样的效果:


$ git stash show -p stash@{0} | git apply -R
同样的,如果你沒有指定具体的某个储藏,Git 会选择最近的储藏:


$ git stash show -p | git apply -R
你可能会想要新建一个別名,在你的 Git 里增加一个 stash-unapply 命令,这样更有效率。例如:


$ git config --global alias.stash-unapply '!git stash show -p | git apply -R'


// 从储藏中创建分支
如果你储藏了一些工作,暂时不去理会,然后继续在你储藏工作的分支上工作,你在重新应用工作时可能会碰到一些问题。
如果尝试应用的变更是针对一个你那之后修改过的文件,你会碰到一个归并冲突并且必须去化解它。
如果你想用更方便的方法来重新检验你储藏的变更,你可以运行 git stash branch,这会创建一个新的分支,
检出你储藏工作时的所处的提交,重新应用你的工作,如果成功,将会丢弃储藏。


// 重写历史


1. 改变最近一次提交
改变最近一次提交也许是最常见的重写历史的行为。对于你的最近一次提交,你经常想做两件基本事情:改变提交说明,
或者改变你刚刚通过增加,改变,删除而记录的快照。


如果你只想修改最近一次提交说明,这非常简单:
$ git commit --amend


** 尽量少用


// 使用 Git 调试
1. 文件标注
可以用git blame来标注文件,查看那个方法的每一行分别是由谁在哪一天修改的。下面这个例子使用了-L选项来限制输出范围在第12至22行:
$ git blame -L 12,22 simplegit.rb


2. 二分查找
$ git bisect start
$ git bisect bad


   
// 子模块
通过git submodule add将外部项目加为子模块:
$ git submodule add git://github.com/chneukirchen/rack.git rack
注意到有一个.gitmodules文件。这是一个配置文件,保存了项目 URL 和你拉取到的本地子目录


$ cat .gitmodules
    [submodule "rack"]
    path = rack
    url = git://github.com/chneukirchen/rack.git
如果你有多个子模块,这个文件里会有多个条目。很重要的一点是这个文件跟其他文件一样也是处于版本控制之下的,就像你的.gitignore文件一样。它跟项目里的其他文件一样可以被推送和拉取。
这是其他克隆此项目的人获知子模块项目来源的途径。    


// 克隆一个带子模块的项目
这里你将克隆一个带子模块的项目。当你接收到这样一个项目,你将得到了包含子项目的目录,但里面没有文件:
$ git clone git://github.com/schacon/myproject.git


rack目录存在了,但是是空的。你必须运行两个命令:git submodule init来初始化你的本地配置文件,
git submodule update来从那个项目拉取所有数据并检出你上层项目里所列的合适的提交:


$ git submodule init
    Submodule 'rack' (git://github.com/chneukirchen/rack.git) registered for path 'rack'
$ git submodule update






// 自定义 Git
1. 配置 Git
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com


git config --help
git config的手册页(译注:以man命令的显示方式)非常细致地罗列了所有可用的配置项。


// core.editor


Git默认会调用你的环境变量editor定义的值作为文本编辑器,如果没有定义的话,会调用Vi来创建和编辑提交以及标签信息, 
你可以使用core.editor改变默认编辑器:
$ git config --global core.editor emacs
现在无论你的环境变量editor被定义成什么,Git 都会调用Emacs编辑信息。


// commit.template


如果把此项指定为你系统上的一个文件,当你提交的时候, Git 会默认使用该文件定义的内容。 
例如:你创建了一个模板文件$HOME/.gitmessage.txt


$ git config --global commit.template $HOME/.gitmessage.txt
$ git commit




// core.pager


core.pager指定 Git 运行诸如log、diff等所使用的分页器,你能设置成用more或者任何你喜欢的分页器(默认用的是less), 
当然你也可以什么都不用,设置空字符串:


$ git config --global core.pager ''
这样不管命令的输出量多少,都会在一页显示所有内容。


// user.signingkey


如果你要创建经签署的含附注的标签(正如第二章所述),那么把你的GPG签署密钥设置为配置项会更好,设置密钥ID如下:
$ git config --global user.signingkey <gpg-key-id>
现在你能够签署标签,从而不必每次运行git tag命令时定义密钥:
$ git tag -s <tag-name>




// core.excludesfile


你能在项目库的.gitignore文件里头用模式来定义那些无需纳入 Git 管理的文件,这样它们不会出现在未跟踪列表,
也不会在你运行git add后被暂存。然而,如果你想用项目库之外的文件来定义那些需被忽略的文件的话,
用core.excludesfile 通知 Git 该文件所处的位置,文件内容和.gitignore类似。


// help.autocorrect


该配置项只在 Git 1.6.1及以上版本有效,假如你在Git 1.6中错打了一条命令,会显示:


$ git com
    git: 'com' is not a git-command. See 'git --help'.


    Did you mean this?
    commit
如果你把help.autocorrect设置成1(译注:启动自动修正),那么在只有一个命令被模糊匹配到的情况下,Git 会自动运行该命令。


// color.ui


Git会按照你需要自动为大部分的输出加上颜色,你能明确地规定哪些需要着色以及怎样着色,设置color.ui为true来打开所有的默认终端着色。


$ git config --global color.ui true
设置好以后,当输出到终端时,Git 会为之加上颜色。其他的参数还有false和always,false意味着不为输出着色,
而always则表明在任何情况下都要着色,即使 Git 命令被重定向到文件或管道。Git 1.5.5版本引进了此项配置,
如果你拥有的版本更老,你必须对颜色有关选项各自进行详细地设置。


你会很少用到color.ui = always,在大多数情况下,如果你想在被重定向的输出中插入颜色码,
你能传递--color标志给 Git 命令来迫使它这么做,color.ui = true应该是你的首选。




// color.*


想要具体到哪些命令输出需要被着色以及怎样着色或者 Git 的版本很老,你就要用到和具体命令有关的颜色配置选项,它们都能被置为true、false或always:


color.branch
color.diff
color.interactive
color.status
除此之外,以上每个选项都有子选项,可以被用来覆盖其父设置,以达到为输出的各个部分着色的目的。例如,
让diff输出的改变信息以粗体、蓝色前景和黑色背景的形式显示:


$ git config --global color.diff.meta “blue black bold”
你能设置的颜色值如:normal、black、red、green、yellow、blue、magenta、cyan、white,正如以上例子设置的粗体属性,
想要设置字体属性的话,可以选择如:bold、dim、ul、blink、reverse。




// core.autocrlf


假如你正在Windows上写程序,又或者你正在和其他人合作,他们在Windows上编程,而你却在其他系统上,在这些情况下,
你可能会遇到行尾结束符问题。这是因为Windows使用回车和换行两个字符来结束一行,而Mac和Linux只使用换行一个字符。
虽然这是小问题,但它会极大地扰乱跨平台协作。


Git可以在你提交时自动地把行结束符CRLF转换成LF,而在签出代码时把LF转换成CRLF。用core.autocrlf来打开此项功能,
如果是在Windows系统上,把它设置成true,这样当签出代码时,LF会被转换成CRLF:


$ git config --global core.autocrlf true
Linux或Mac系统使用LF作为行结束符,因此你不想 Git 在签出文件时进行自动的转换;
当一个以CRLF为行结束符的文件不小心被引入时你肯定想进行修正,把core.autocrlf设置成input来告诉 Git 在提交时把CRLF转换成LF,
签出时不转换:


$ git config --global core.autocrlf input
这样会在Windows系统上的签出文件中保留CRLF,会在Mac和Linux系统上,包括仓库中保留LF。


如果你是Windows程序员,且正在开发仅运行在Windows上的项目,可以设置false取消此功能,把回车符记录在库中:


$ git config --global core.autocrlf false   




// core.whitespace


Git预先设置了一些选项来探测和修正空白问题,其4种主要选项中的2个默认被打开,另2个被关闭,你可以自由地打开或关闭它们。


默认被打开的2个选项是trailing-space和space-before-tab,trailing-space会查找每行结尾的空格,
space-before-tab会查找每行开头的制表符前的空格。


默认被关闭的2个选项是indent-with-non-tab和cr-at-eol,indent-with-non-tab会查找8个以上空格(非制表符)开头的行,
cr-at-eol让 Git 知道行尾回车符是合法的。


设置core.whitespace,按照你的意图来打开或关闭选项,选项以逗号分割。通过逗号分割的链中去掉选项或在选项前加-来关闭,
例如,如果你想要打开除了cr-at-eol之外的所有选项:


$ git config --global core.whitespace \
    trailing-space,space-before-tab,indent-with-non-tab




// receive.fsckObjects


Git默认情况下不会在推送期间检查所有对象的一致性。虽然会确认每个对象的有效性以及是否仍然匹配SHA-1检验和,但 Git 不会在每次推送时都检查一致性。对于 Git 来说,库或推送的文件越大,这个操作代价就相对越高,每次推送会消耗更多时间,如果想在每次推送时 Git 都检查一致性,设置 receive.fsckObjects 为true来强迫它这么做:


$ git config --system receive.fsckObjects true
现在 Git 会在每次推送生效前检查库的完整性,确保有问题的客户端没有引入破坏性的数据。
   


// 识别二进制文件


在.gitattributes文件中设置如下:
*.pbxproj -crlf -diff    


现在,Git 会尝试转换和修正CRLF(回车换行)问题,也不会当你在项目中运行git show或git diff时,比较不同的内容。
在Git 1.6及之后的版本中,可以用一个宏代替-crlf -diff:
*.pbxproj binary


// 比较二进制文件


在Git 1.6及以上版本中,你能利用 Git 属性来有效地比较二进制文件。可以设置 Git 把二进制数据转换成文本格式,用通常的diff来比较。


你不能直接比较两个不同版本的Word文件,除非进行手动扫描,不是吗? Git 属性能很好地解决此问题,把下面的行加到.gitattributes文件:


*.doc diff=word
当你要看比较结果时,如果文件扩展名是"doc",Git 调用"word"过滤器。什么是"word"过滤器呢?其实就是 Git 使用strings 程序,把Word文档转换成可读的文本文件,之后再进行比较:


$ git config diff.word.textconv strings
现在如果在两个快照之间比较以.doc结尾的文件,Git 对这些文件运用"word"过滤器,在比较前把Word文件转换成文本文件。


// 导出仓库


Git属性在导出项目归档时也能发挥作用。


// export-ignore


当产生一个归档时,可以设置 Git 不导出某些文件和目录。如果你不想在归档中包含一个子目录或文件,但想他们纳入项目的版本管理中,
你能对应地设置export-ignore属性。


例如,在test/子目录中有一些测试文件,在项目的压缩包中包含他们是没有意义的。因此,可以增加下面这行到 Git 属性文件中:


test/ export-ignore
现在,当运行git archive来创建项目的压缩包时,那个目录不会在归档中出现。


// export-subst


还能对归档做一些简单的关键字替换。在第2章中已经可以看到,可以以--pretty=format形式的简码在任何文件中放入$Format:$ 字符串。例如,如果想在项目中包含一个叫作LAST_COMMIT的文件,当运行git archive时,最后提交日期自动地注入进该文件,可以这样设置:


$ echo 'Last commit date: $Format:%cd$' > LAST_COMMIT
    $ echo "LAST_COMMIT export-subst" >> .gitattributes
    $ git add LAST_COMMIT .gitattributes
    $ git commit -am 'adding LAST_COMMIT file for archives'
运行git archive后,打开该文件,会发现其内容如下:


$ cat LAST_COMMIT
    Last commit date: $Format:Tue Apr 21 08:38:48 2009 -0700$




// Git挂钩
//安装一个挂钩


挂钩都被存储在 Git 目录下的hooks子目录中,即大部分项目中的.git/hooks。 Git 默认会放置一些脚本样本在这个目录中,
除了可以作为挂钩使用,这些样本本身是可以独立使用的。所有的样本都是shell脚本,其中一些还包含了Perl的脚本,不过,
任何正确命名的可执行脚本都可以正常使用 — 可以用Ruby或Python,或其他。在Git 1.6版本之后,这些样本名都是以.sample结尾,
因此,你必须重新命名。在Git 1.6版本之前,这些样本名都是正确的,但这些样本不是可执行文件。


把一个正确命名且可执行的文件放入 Git 目录下的hooks子目录中,可以激活该挂钩脚本,因此,之后他一直会被 Git 调用。
随后会讲解主要的挂钩脚本。


客户端挂钩


有许多客户端挂钩,以下把他们分为:提交工作流挂钩、电子邮件工作流挂钩及其他客户端挂钩。


1. 提交工作流挂钩


有 4个挂钩被用来处理提交的过程。


pre-commit挂钩在键入提交信息前运行,被用来检查即将提交的快照,
例如,检查是否有东西被遗漏,确认测试是否运行,以及检查代码。当从该挂钩返回非零值时,Git 放弃此次提交,
但可以用git commit --no-verify来忽略。该挂钩可以被用来检查代码错误(运行类似lint的程序),检查尾部空白(默认挂钩是这么做的),
检查新方法(译注:程序的函数)的说明。


prepare-commit-msg挂钩在提交信息编辑器显示之前,默认信息被创建之后运行。因此,可以有机会在提交作者看到默认信息前进行编辑。
该挂钩接收一些选项:拥有提交信息的文件路径,提交类型,如果是一次修订的话,提交的SHA-1校验和。该挂钩对通常的提交来说不是很有用,
只在自动产生的默认提交信息的情况下有作用,如提交信息模板、合并、压缩和修订提交等。可以和提交模板配合使用,以编程的方式插入信息。


commit-msg挂钩接收一个参数,此参数是包含最近提交信息的临时文件的路径。如果该挂钩脚本以非零退出,Git 放弃提交,
因此,可以用来在提交通过前验证项目状态或提交信息


post-commit挂钩在整个提交过程完成后运行,他不会接收任何参数,但可以运行git log -1 HEAD来获得最后的提交信息。
总之,该挂钩是作为通知之类使用的。


update


update 脚本和 pre-receive 脚本十分类似。不同之处在于它会为推送者更新的每一个分支运行一次。
假如推送者同时向多个分支推送内容,pre-receive 只运行一次,相比之下 update 则会为每一个更新的分支运行一次。
它不会从标准输入读取内容,而是接受三个参数:索引的名字(分支),推送前索引指向的内容的 SHA-1 值,
以及用户试图推送内容的 SHA-1 值。如果 update 脚本以退出时返回非零值,只有相应的那一个索引会被拒绝;
其余的依然会得到更新。




// Git 内部原理


从根本上来讲 Git 是一套内容寻址 (content-addressable) 文件系统,在此之上提供了一个 VCS 用户界面


 git init 时,Git 会创建一个 .git 目录,几乎所有 Git 存储和操作的内容都位于该目录下。如果你要备份或复制一个库,基本上将这一目录拷贝至其他地方就可以了。本章基本上都讨论该目录下的内容。该目录结构如下:


$ ls
    HEAD
    branches/
    config
    description
    hooks/
    index
    info/
    objects/
    refs/
    


branches   : 新版本的 Git 不再使用这个目录
description : 文件仅供 GitWeb 程序使用,所以不用关心这些内容
config : 文件包含了项目特有的配置选项
info : 目录保存了一份不希望在 .gitignore 文件中管理的忽略模式 (ignored patterns) 的全局可执行文件
hooks : 目录保存了第七章详细介绍了的客户端或服务端钩子脚本。


四个重要的文件或目录:HEAD , index 文件 ,objects 及 refs 目录。
objects : 目录存储所有数据内容
refs : 目录存储指向数据 (分支) 的提交对象的指针
HEAD : 文件指向当前分支
index : 文件保存了暂存区域信息




// Git 对象


$ echo 'test content' | git hash-object -w --stdin
    d670460b4b4aece5915caf5c68d12f560a9fe3e4
    
参数 -w 指示 hash-object 命令存储 (数据) 对象,若不指定这个参数该命令仅仅返回键值。--stdin 指定从标准输入设备 (stdin) 来读取内容,若不指定这个参数则需指定一个要存储的文件的路径。该命令输出长度为 40 个字符的校验和。这是个 SHA-1 哈希值──其值为要存储的数据加上你马上会了解到的一种头信息的校验和。现在可以查看到 Git 已经存储了数据:


$ find .git/objects -type f
    .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4   
    
可以在 objects 目录下看到一个文件。这便是 Git 存储数据内容的方式──为每份内容生成一个文件,取得该内容与头信息的 SHA-1 校验和,
创建以该校验和前两个字符为名称的子目录,并以 (校验和) 剩下 38 个字符为文件命名 (保存至子目录下)。


通过 cat-file 命令可以将数据内容取回。该命令是查看 Git 对象的瑞士军刀。传入 -p 参数可以让该命令输出数据内容的类型:


$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
    test content


可以往 Git 中添加更多内容并取回了。也可以直接添加文件。比方说可以对一个文件进行简单的版本控制。首先,创建一个新文件,并把文件内容存储到数据库中:


$ echo 'version 1' > test.txt
    $ git hash-object -w test.txt
    83baae61804e65cc73a7201a7252750c76066a30
接着往该文件中写入一些新内容并再次保存:


$ echo 'version 2' > test.txt
    $ git hash-object -w test.txt
    1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
数据库中已经将文件的两个新版本连同一开始的内容保存下来了:


$ find .git/objects -type f
    .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
    .git/objects/83/baae61804e65cc73a7201a7252750c76066a30
    .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
再将文件恢复到第一个版本:


$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
    $ cat test.txt
    version 1
或恢复到第二个版本:


$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
    $ cat test.txt
    version 2
需要记住的是几个版本的文件 SHA-1 值可能与实际的值不同,其次,存储的并不是文件名而仅仅是文件内容。这种对象类型称为 blob 。
通过传递 SHA-1 值给 cat-file -t 命令可以让 Git 返回任何对象的类型:


$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
    blob
    


// tree (树) 对象


接下去来看 tree 对象,tree 对象可以存储文件名,同时也允许存储一组文件。Git 以一种类似 UNIX 文件系统但更简单的方式来存储内容。
所有内容以 tree 或 blob 对象存储,其中 tree 对象对应于 UNIX 中的目录,blob 对象则大致对应于 inodes 或文件内容。
一个单独的 tree 对象包含一条或多条 tree 记录,每一条记录含有一个指向 blob 或子 tree 对象的 SHA-1 指针,
并附有该对象的权限模式 (mode)、类型和文件名信息。以 simplegit 项目为例,最新的 tree 可能是这个样子:


$ git cat-file -p master^{tree}
    100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
    100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
    040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
master^{tree} 表示 branch 分支上最新提交指向的 tree 对象。请注意 lib 子目录并非一个 blob 对象,而是一个指向别一个 tree 对象的指针:


$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
    100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
    
tree
↓  ↓  ↓
       readme makefile lib
       ↓      ↓        ↓
      blob   blob     tree   
                       ↓
                      simplegit.rb


你可以自己创建 tree 。通常 Git 根据你的暂存区域或 index 来创建并写入一个 tree 。因此要创建一个 tree 对象的话首先要通过将一些文件暂存从而创建一个 index 。可以使用 plumbing 命令 update-index 为一个单独文件 ── test.txt 文件的第一个版本 ── 创建一个 index 。通过该命令人为的将 test.txt 文件的首个版本加入到了一个新的暂存区域中。由于该文件原先并不在暂存区域中 (甚至就连暂存区域也还没被创建出来呢) ,必须传入 --add 参数;由于要添加的文件并不在当前目录下而是在数据库中,必须传入 --cacheinfo 参数。同时指定了文件模式,SHA-1 值和文件名:


$ git update-index --add --cacheinfo 100644 \
    83baae61804e65cc73a7201a7252750c76066a30 test.txt
在本例中,指定了文件模式为 100644,表明这是一个普通文件。其他可用的模式有:100755 表示可执行文件,120000 表示符号链接。文件模式是从常规的 UNIX 文件模式中参考来的,但是没有那么灵活 ── 上述三种模式仅对 Git 中的文件 (blobs) 有效 (虽然也有其他模式用于目录和子模块)。


现在可以用 write-tree 命令将暂存区域的内容写到一个 tree 对象了。无需 -w 参数 ── 如果目标 tree 不存在,
调用 write-tree 会自动根据 index 状态创建一个 tree 对象。


$ git write-tree
    d8329fc1cc938780ffdd9f94e0d364e0ea74f579
    $ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
    100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
可以这样验证这确实是一个 tree 对象:


$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
    tree
再根据 test.txt 的第二个版本以及一个新文件创建一个新 tree 对象:


$ echo 'new file' > new.txt
    $ git update-index test.txt
    $ git update-index --add new.txt
这时暂存区域中包含了 test.txt 的新版本及一个新文件 new.txt 。创建 (写) 该 tree 对象 (将暂存区域或 index 状态写入到一个 tree 对象),然后瞧瞧它的样子:


$ git write-tree
    0155eb4229851634a0f03eb265b69f5a2d56f341
    $ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
    100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
    100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
请注意该 tree 对象包含了两个文件记录,且 test.txt 的 SHA 值是早先值的 "第二版" (1f7a7a)。来点更有趣的,你将把第一个 tree 对象作为一个子目录加进该 tree 中。可以用 read-tree 命令将 tree 对象读到暂存区域中去。在这时,通过传一个 --prefix 参数给 read-tree,将一个已有的 tree 对象作为一个子 tree 读到暂存区域中:


$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
    $ git write-tree
    3c4e9cd789d88d8d89c1073707c3585e41b0e614
    $ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
    040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
    100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
    100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt


// commit (提交) 对象


// 对象存储
    
// Git References


// HEAD 标记


// Tags


// Remotes


// Packfiles


// The Refspec


// 推送 Refspec


    
    




  
  
    












    




    




    
     
    



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值