TranslateProject/translated/tech/20150211 25 Tips for Intermediate Git Users.md

21 KiB
Raw Blame History

25个给git熟手的技巧

我已经使用git差不多18个月了觉得自己对它应该已经非常了解。然后来自GitHub的Scott Chacon过来给LVS做培训LVS是一个赌博软件供应商和开发商从2013年开始的合同而我在第一天里就学到了很多。

作为一个对git感觉良好的人我觉得分享从社区里掌握的一些有价值的信息也许能帮某人解决问题而不用做太深入研究。

基本技巧

1. 安装后的第一步

在安装好git后你第一件该做的事是设置你的名字和电子邮箱因为每次提交都要用到这些信息

$ git config --global user.name "Some One"
$ git config --global user.email "someone@gmail.com"

2. Git是基于指针的

保存在git里的一切都是文件。当你创建一个提交的时候会建立一个包含你的提交信息和相关数据名字邮件地址日期/时间前一个提交等等的文件并把它链接到一个文件树中。文件树中包含了对象或其他树的列表。对象或容器是和本次提交相关的实际内容也是一个文件你想了解的话尽管文件名并没有包含在对象里而是在树中。所有这些文件都使用对象的SHA-1哈希值作为文件名。

用这种方式分支和标签就是简单的文件基本上是这样包含指向实际提交的SHA-1哈希值。使用这些索引会带来优秀的灵活性和速度比如创建一个新分支就只要简单地创建一个包含分支名字和所分出的那个提交的SHA-1索引的文件。当然你不需要自己做这些而只要使用Git命令行工具或者GUI但是实际上就是这么简单。

你也许听说过叫HEAD的索引。这只是简单的一个文件包含了你当前指向的那个提交的SHA-1索引值。如果你正在解决一次合并冲突然后看到了HEAD这并不是一个特别的分支或分值上一个必须的特殊点只是标明你当前所在位置。

所有的分支指针都保存在.git/refs/heads里HEAD在.git/HEAD里而标签保存在.git/refs/tags里 - 自己可以放心地进去看看。

3. 两个父节点 - 当然!

在历史中查看一个合并提交的信息时,你将看到有两个父节点(相对于一般工作上的常规提交的情况)。第一个父节点是你所在的分支,第二个是你合并过来的分支。

4. 合并冲突

目前我相信你碰到过合并冲突并且解决过。通常是编辑一下文件,去掉<<<<====>>>>标志,保留需要留下的代码。有时能够看到这两个修改之前的代码会很不错,比如,在这两个分支上有冲突的改动之前。下面是一种方式:

$ git diff --merge
diff --cc dummy.rb  
index 5175dde,0c65895..4a00477  
--- a/dummy.rb
+++ b/dummy.rb
@@@ -1,5 -1,5 +1,5 @@@
  class MyFoo
    def say
-     puts "Bonjour"
 -    puts "Hello world"
++    puts "Annyong Haseyo"
    end
  end

如果是二进制文件,比较差异就没那么简单了...通常你要做的就是测试这个二进制文件的两个版本来决定保留哪个或者在二进制文件编辑器里手工复制冲突部分。从一个特定分支获取文件拷贝比如说你在合并master和feature123

$ git checkout master flash/foo.fla # 或者...
$ git checkout feature132 flash/foo.fla
$ # 然后...
$ git add flash/foo.fla

另一种方式是通过git输出文件 - 你可以输出到另外的文件名,然后再重命名正确的文件(当你决定了要用哪个)为正常的文件名:

$ git show master:flash/foo.fla > master-foo.fla
$ git show feature132:flash/foo.fla > feature132-foo.fla
$ # 检出master-foo.fla和feature132-foo.fla
$ # 假如说我们决定来自feature132的文件是正确的
$ rm flash/foo.fla
$ mv feature132-foo.fla flash/foo.fla
$ rm master-foo.fla
$ git add flash/foo.fla

更新感谢Carl在原博客文章上评论里的提醒你实际上可以用“git checkout —ours flash/foo.fla”和“git checkout —theirs flash/foo.fla”来检出特定版本的文件而不用记住你在合并的分支名字。就我个人来说喜欢更精确一点但这也是一种方式...

记着在解决完冲突后要将文件加入提交(像我上面做的那样)。

服务器,分支和标签

5. 远端服务器

git的一个超强大的功能就是可以有不止一个远端服务器实际上你一直都在一个本地仓库上工作。你并不是一定都要有写权限你可以有多个可以读取的服务器用来合并他们的工作然后写入其他仓库。添加一个新的远端服务器很简单

$ git remote add john git@github.com:johnsomeone/someproject.git

如果你想查看远端服务器的信息可以这样做:

# 显示每个远端服务器的URL
$ git remote -v 

# 提供更多详细信息
$ git remote show name 

你随时都可以查看本地分支和远端分支的差异:

$ git diff master..john/master

你也可以查看不在远端分支的HEAD的改动

$ git log remote/branch..
# 注意:..后面没有结束的refspec

6. 标签

在git里有两种类型的标签 - 轻量级标签和带注释标签。记住技巧2里说过git是基于指针的这两者之间的差异也很简单。轻量级标签只是一个简单的指向一次提交的带名字指针。你随时都可以将它指向另一个提交。带注释标签是一个指向标签对象的带名字指针带有自己的信息和历史。因为有自己的信息它可以根据需要用GPG签名。

建立这两种类型的标签都很简单(只有一个命令行开关的差异)

$ git tag to-be-tested
$ git tag -a v1.1.0 # 会提示输入标签信息

7. 建立分支

在git里建立分支非常简单而且像闪电一样快因为它只需要创建一个小于100字节的文件。用普通方式建立新分支并切换过去

$ git branch feature132
$ git checkout feature132

当然,如果你确定自己要新建分支并直接切换过去,可以用一个命令实现:

$ git checkout -b feature132

如果你想重命名一个本地分支也很简单(可以显示发生了什么的较长的方式):

$ git checkout -b twitter-experiment feature132
$ git branch -d feature132

更新你也可以像Brian Palmer在原博客文章的评论里提出的只用“git branch”的-m开关在一个命令里实现像Mike提出的如果你只有一个分支参数就会重命名当前分支

$ git branch -m twitter-experiment
$ git branch -m feature132 twitter-experiment

8. 合并分支

在将来什么时候,你希望合并改动。有两种方式:

$ git checkout master
$ git merge feature83 # 或者...
$ git rebase feature83

merge和rebase之间的差别是merge会尝试处理改动并建立一个新的混合了两者的提交。rebase会尝试把你从一个分支最后一次分离后的所有改动一个个加到该分支的HEAD上。不过在已经将分支推到远端服务器后不要再rebase了 - 这回引起冲突/问题。

如果你不确定在哪些分支上还有独有的工作 - 所以你也不知道哪些分支需要合并而哪些可以删除git branch有两个开关可以帮你

# 显示已经全部合并到当前分支的分支
$ git branch --merged

# 显示没有合并到当前分支的分支
$ git branch --no-merged

9. 远端分支

如果你在本地有一个分支希望推到远端服务器上,你可以用一行命令推送上去:

$ git push origin twitter-experiment:refs/heads/twitter-experiment
# origin是我们服务器的名字而twitter-experiment是分支名字

更新感谢Erlend在原博客文章上的评论 - 这个实际上和git push origin twitter-experiment效果一样,不过使用完整的语法,你可以在两者之间使用不同的分知名(这样本地分支可以是add-ssl-support而远端是issue-1723)。

如果你想在远端服务器上删除一个分支(注意分支名前面的冒号):

$ git push origin :twitter-experiment

如果你想查看所有远端分支的状态可以这样做:

$ git remote show origin

这个命令可能会列出服务器上一些以前有过但现在已经不在了的分支。如果碰到这种情况你可以用下面的命令从你本地分支里清理掉:

$ git remote prune

最后,如果你想在本地跟踪一个远端分支,普通的方式是:

$ git branch --track myfeature origin/myfeature
$ git checkout myfeature

不过新版的git在使用-b标记检出分支时会自动设定跟踪

$ git checkout -b myfeature origin/myfeature

在储藏点,索引和文件系统中保存内容

10. 储藏

在git里你可以把当前工作状态放进一个储藏堆栈中然后可以再取出来。最简单的情形是下面这样

$ git stash
# 做点其他事情...
$ git stash pop

许多人建议使用git stash apply来代替pop不过如果这样做的话最后会遗留一个很长的储藏列表。而“pop”会在全部加载后自动从堆栈中移除。如果使用过git stash apply,你也可以使用下面的命令从堆栈上移除最后一项:

$ git stash drop

git会基于当前的提交信息自动创建评论。如果你更希望有自定义信息的话因为它可能和前一个提交没有任何联系

$ git stash save "My stash message"

如果你希望从列表中取出一个特定的储藏点(不一定非得是最后一个)可以先列出它们然后用下面的方式取出:

$ git stash list
  stash@{0}: On master: Changed to German
  stash@{1}: On master: Language is now Italian
$ git stash apply stash@{1}

11. 交互式添加

在subversion的世界里你只能修改文件然后提交所有改动。而在git里你有强大得多的方式来提交部分文件或者甚至是部分补丁。提交部分文件或文件中的部分改动你需要进入交互式模式

$ git add -i
           staged     unstaged path


*** Commands ***
  1: status      2: update   3: revert   4: add untracked
  5: patch      6: diff     7: quit     8: help
What now>  

这会让你进入一个基于菜单的交互式提示。你可以使用命令中的数字或高亮的字母如果你在终端里打开了高亮的话来进入相应的模式。然后就只是输入你希望操作的文件的数字了你可以使用这样的格式1或者1-4或2,4,7

如果你想进入补丁模式交互式模式下的p5你也可以直接进入

$ git add -p    
diff --git a/dummy.rb b/dummy.rb  
index 4a00477..f856fb0 100644  
--- a/dummy.rb
+++ b/dummy.rb
@@ -1,5 +1,5 @@
 class MyFoo
   def say
-    puts "Annyong Haseyo"
+    puts "Guten Tag"
   end
 end
Stage this hunk [y,n,q,a,d,/,e,?]?  

你可以看到下方会有一些选项供选择用来添加该文件的这个改动,该文件的所有改动,等等。使用‘?’命令可以详细解释这些选项。

12. 从文件系统里保存/取回改动

有些项目比如git项目本身在git文件系统中直接保存额外文件而并没有将它们加入到版本控制中。

让我们从在git中存储一个随机文件开始

$ echo "Foo" | git hash-object -w --stdin
51fc03a9bb365fae74fd2bf66517b30bf48020cb  

这样这个目标文件就已经保存到数据库中了,但是如果你没有设定一个指向它的指针的话它会被当做垃圾回收。最简单的方式是设定一个标签:

$ git tag myfile 51fc03a9bb365fae74fd2bf66517b30bf48020cb

注意这里我们使用了标签myfile。当我们需要使用这个文件的时候可以这样做

$ git cat-file blob myfile

这个对于一些工具文件很有用开发者可能会用到密码GPG密钥等等但是又不希望每次都检出到硬盘尤其是在实际工作中

日志以及有哪些改动?

13. 查看日志

如果不用git log来查看最近的提交你git用不了多久。不过有一些技巧来更好地应用。比如你可以使用下面的命令来查看每次提交的具体改动

$ git log -p

或者你可以仅仅查看有哪些文件改动:

$ git log --stat

有个很不错的别名你可以试试会显示简短提交名和一个不错的分支图并在一行里显示提交信息有点像gitk但是是在命令行下

$ git config --global alias.lol "log --pretty=oneline --abbrev-commit --graph --decorate"
$ git lol
* 4d2409a (master) Oops, meant that to be in Korean
* 169b845 Hello world

14. 搜索日志

如果你想找特定作者可以这样做:

$ git log --author=Andy

更新感谢Johannes的评论我已经去掉了之前这里的一些有混淆的地方。

或者你想在提交信息里找一些相关字段:

$ git log --grep="Something in the message"

也有一个更强大的叫做pickaxe的命令用来查找删除或添加某个特定内容的提交比如该文件第一次出现或被删除。这可以告诉你什么时候增加了一行但这一行里的某个字符后面被改动过就不行了

$ git log -S "TODO: Check for admin status"

假如你改动了一个特定的文件,比如lib/foo.rb

$ git log lib/foo.rb

比如说你有一个feature/132分支和feature/145分支然后你想看看这两个分支上不在master分支里的提交注意^符号是不在的意思):

$ git log feature/132 feature/145 ^master

你也可以使用ActiveSupport格式的日期来缩小到某个日期范围

$ git log --since=2.months.ago --until=1.day.ago

默认情况下会用OR来组合查询但你可以轻易地改为AND如果你有超过一条的标准

$ git log --since=2.months.ago --until=1.day.ago --author=andy -S "something" --all-match

15. 查看/修改版本

有很多方式可以用来引用一个版本,看你记得哪个:

$ git show 12a86bc38 # 根据版本
$ git show v1.0.1 # 根据标签
$ git show feature132 # 根据分支名
$ git show 12a86bc38^ # 一次提交的父节点
$ git show 12a86bc38~2 # 一次提交的祖父节点
$ git show feature132@{yesterday} # 时间相关
$ git show feature132@{2.hours.ago} # 时间相关

注意和之前部分有些不同,末尾的插入符号意思是该提交的父节点 - 开始位置的插入符号意思是不在这个分支。

16. 选择范围

最简单的方式:

$ git log origin/master..new
# [old]..[new] - 所有你还没有推送的提交

你也可以省略[new]将使用当前的HEAD。

Rewinding Time & Fixing Mistakes

17. 重置改动

如果你还没有提交的话可以用下面的命令轻松地取消改动:

$ git reset HEAD lib/foo.rb

通常会使用unstage的别名因为看上去有些不直观。

$ git config --global alias.unstage "reset HEAD"
$ git unstage lib/foo.rb

如果你已经提交了该文件,你可以做两件事 - 如果是最后一次提交你还可以改正:

$ git commit --amend

这会取消最后一次提交,把工作分支回退到提交前标记了所有改动的状态,而且提交信息也都准备好可以修改或直接提交。

如果你已经提交过多次而且希望全部回退,你可以将分支重置到合适的位置。

$ git checkout feature132
$ git reset --hard HEAD~2

如果你实际上希望将分支指向一个完全不同的SHA1也许你要将一个分支的HEAD替换到另一个分支或者之后的某次提交你可以使用下面的较长的方式

$ git checkout FOO
$ git reset --hard SHA

实际上有一个快速的方式不需要先把你的工作分支切换到FOO再前进到SHA

$ git update-ref refs/heads/FOO SHA

18. 提交到了错误的分支

好吧假如说你已经提交到了master但却应该创建一个叫experimental的主题分支更合适。要移动这些改动你可以在当前位置创建分支回退HEAD再检出新分支

$ git branch experimental   # 创建一个指向当前master的位置的指针
$ git reset --hard master~3 # 移动master分支的指针到3个版本之前
$ git checkout experimental

如果你的改动是在分支的分支的分支上会更复杂。那样你需要做的是将分支基础切换到其他地方:

$ git branch newtopic STARTPOINT
$ git rebase oldtopic --onto newtopic

19. 交互式切换基础

这是一个我之前看过展示却没真正理解过的很赞的功能现在很简单。假如说你提交了3次但是你希望更改顺序或编辑或者合并

$ git rebase -i master~3

然后会启动你的编辑器并带有一些指令。你所要做的就是修改这些指令来选择/插入/编辑(或者删除)提交和保存/退出。然后在编辑完后你可以用git rebase --continue命令来让每一条指令生效。

如果你有修改将会切换到你提交时所处的状态之后你需要使用命令git commit --amend来编辑。

注意在rebase的时候千万不要提交 - 只能先添加然后使用参数--continue--skip或--abort。

20. 清理

如果你提交了一些内容到你的分支也许你从SVN导入了一些旧仓库然后你希望把某个文件从历史记录中全部删掉

$ git filter-branch --tree-filter 'rm -f *.class' HEAD

如果你已经推送到origin了但之后提交了一些垃圾改动你也可以在推送前在本地系统里这样做

$ git filter-branch --tree-filter 'rm -f *.class' origin/master..HEAD

其他技巧

21. 你查看过的前一个引用

如果你知道自己之前查看过一个SHA-1但是随后做了一些重置/回退的操作你可以使用reflog命令来列出最近查看过的SHA-1记录

$ git reflog
$ git log -g # 和上面一样,但是使用'log'格式输出

22. 分支命名

一个可爱的小技巧 - 别忘了分支名并不限于a-z和0-9。名字中可以用/和.将非常方便用来建立伪命名空间或版本,例如:

$ # 生成版本132的改动历史
$ git shortlog release/132 ^release/131
$ # 贴上v1.0.1的标签
$ git tag v1.0.1 release/132

23. 找出谁是凶手

通常找出来谁改动了某个文件里的某行代码会很有用。实现这个功能的最简单命令是:

$ git blame FILE

有时候这些改动来自其他文件(如果你合并了两个文件,或者你移动了某个函数)所以你可以使用下面的命令:

$ # 显示内容来自哪个文件
$ git blame -C FILE

有时候通过点击各个改动然后回到很早很早以前来跟踪改动会很不错。有一个很好的内建GUI命令来做这个

$ git gui blame FILE

24. 数据维护

通常git不需要经常维护它把自己照顾的很好。不过你可以通过下面的命令查看数据统计

$ git count-objects -v

如果占用很多空间的话,你可以选择在你的本地仓库做垃圾回收。这不会影响推送或其他人,却会让一些命令运行更快而且减少空间占用:

$ git gc

经常运行完整性检查也很有意义:

$ git fsck --full

你也可以在末尾加上--auto参数如果你在服务器上通过crontab经常/每天都运行这个命令的话然后它只会在必要的时候才执行fsck动作。

在检查的时候看到“dangling”或“unreachable”是正常的通常这是由回退HEAD或切换基础的结果。而看到“missing”或“sha1 mismatch”就不对了...找专业人士帮忙吧!

25. 恢复遗失的分支

如果你使用-D参数删除了experimental分支可以用下面的命令重新建立

$ git branch experimental SHA1_OF_HASH

如果你访问过的话你通常可以用git reflog来找到SHA1哈希值。

另一种方式是使用git fsck —lost-found。其中一个dangling的提交就是丢失的HEAD它只是已删除分支的HEAD而HEAD^被引用为当前的HEAD所以它并不处于dangling状态

搞定!

哇,这是我写过的最长的一篇博客,我希望有人能觉得有用。如果你这么觉得,或者你有任何疑问请在评论里留言让我知道...


via: https://www.andyjeffries.co.uk/25-tips-for-intermediate-git-users/

作者:Andy Jeffries 译者:zpl1025 校对:校对者ID

本文由 LCTT 原创翻译,Linux中国 荣誉推出