git代码库工作流


从日常git代码库使用和管理的角度来总结一下git相关的命令使用。

1.新增代码

git add <source-code>
git commit -m '<commit log>'

2.回退代码

命令 作用域 常用情景
git reset 提交层面 在私有分支上舍弃一些没有提交的更改
git reset 文件层面 将文件从缓存区中移除
git checkout 提交层面 切换分支或查看旧版本
git checkout 文件层面 舍弃工作目录中的更改
git revert 提交层面 在公共分支上回滚更改

除了在当前分支上操作,还可以通过传入这些标记来修改你的缓存区或工作目录:

  1. –soft – 缓存区和工作目录都不会被改变
  2. –mixed – 默认选项。缓存区和你指定的提交同步,但工作目录不受影响
  3. –hard – 缓存区和工作目录都同步到你指定的提交
  • git checkout

    这个命令有三个不同的作用:签出文件、签出提交和签出分支。签出提交会使工作目录和这个提交完全匹配。你可以用它来查看项目之前的状态,而不改变当前的状态。签出文件使你能够查看某个特定文件的旧版本,而工作目录中剩下的文件不变。

  • git revert

    命令用来撤销一个已经提交的快照。但是,它是通过搞清楚如何撤销这个提交引入的更改,然后在最后加上一个撤销了更改的 提交,而不是从项目历史中移除这个提交。这避免了Git丢失项目历史,这一点对于你的版本历史和协作的可靠性来说是很重要的。撤销(revert)应该用在你想要在项目历史中移除一整个提交的时候。比如说,你在追踪一个bug,然后你发现它是由一个提交造成的,这时候撤销就很有用。与其说自己去修复它,然后提交一个新的快照,git revert帮你做了所有的事情。

  • git reset

    命令用来重设更改时(提交不再被任何引用或引用日志所引用),我们无法获得原来的样子——这个撤销是永远的。使用这个工具的时候务必要小心,因为这是少数几个可能会造成工作丢失的命令之一。撤销(revert)被设计为撤销 公开 的提交的安全方式,git reset被设计为重设 本地 更改。因为两个命令的目的不同,它们的实现也不一样:重设完全地移除了一堆更改,而撤销保留了原来的更改,用一个新的提交来实现撤销。当有之后的提交被推送到公共仓库后,你绝不应该使用git reset。发布一个提交之后,你必须假设其他开发者会依赖于它。

    移除一个其他团队成员在上面继续开发的提交在协作时会引发严重的问题。当他们试着和你的仓库同步时,他们会发现项目历史的一部分突然消失了。origin/master是你本地master分支对应的中央仓库中的分支。一旦你在重设之后又增加了新的提交,Git会认为你的本地历史已经和origin/master分叉了,同步你的仓库时的合并提交(merge commit)会使你的同事困惑。重点是,确保你只对本地的修改使用git reset——而不是公共更改。如果你需要修复一个公共提交,git revert命令正是被设计来做这个的。

    git reset 
    

    从缓存区移除特定文件,但不改变工作目录。它会取消这个文件的缓存,而不覆盖任何更改。

    git reset
    

    重设缓冲区,匹配最近的一次提交,但工作目录不变。它会取消 所有 文件的缓存,而不会覆盖任何修改,给你了一个重设缓存快照的机会。

    git reset --hard
    

    重设缓冲区和工作目录,匹配最近的一次提交。除了取消缓存之外,--hard 标记告诉Git还要重写所有工作目录中的更改。换句话说:它清除了所有未提交的更改,所以在使用前确定你想扔掉你所有本地的开发。

    git reset 
    

    将当前分支的末端移到,将缓存区重设到这个提交,但不改变工作目录。所有之后的更改会保留在工作目录中,这允许你用更干净、原子性的快照重新提交项目历史。

    git reset --hard 
    

    将当前分支的末端移到,将缓存区和工作目录都重设到这个提交。它不仅清除了未提交的更改,同时还清除了之后的所有提交。

3.分支使用

在Git中,分支是你日常开发流程中的一部分。当你想要添加一个新的功能或是修复一个bug时——不管bug是大是小——你都应该新建一个分支来封装你的修改。这确保了不稳定的代码永远不会被提交到主代码库中,它同时给了你机会,在并入主分支前清理你feature分支的历史。

  • git branch

    git branch
    

    列出仓库中所有分支。

    git branch 
    

    创建一个名为的分支。不会 自动切换到那个分支去。

    git branch -d 
    

    删除指定分支。这是一个“安全”的操作,Git会阻止你删除包含未合并更改的分支。

    git branch -D 
    

    强制删除指定分支,即使包含未合并更改。如果你希望永远删除某条开发线的所有提交,你应该用这个命令。

    git branch -m 
    

    将当前分支命名为

  • git checkout

    git checkout 
    

    查看特定分支,分支应该已经通过git branch创建。这使得成为当前的分支,并更新工作目录的版本。

    git checkout -b 
    

    创建并查看-b选项是一个方便的标记,告诉Git在运行git checkout 之前运行git branch

    git checkout -b  
    

    和上一条相同,但将作为新分支的基,而不是当前分支。

  • git merge

    Git会决定使用哪种合并算法。当当前分支顶端到目标分支路径是线性之时,我们可以采取 快速向前合并 。Git只需要将当前分支顶端(快速向前地)移动到目标分支顶端,即可整合两个分支的历史,而不需要“真正”合并分支。它在效果上合并了历史,因为目标分支上的提交现在在当前分支可以访问到。但是,如果分支已经分叉了,那么就无法进行快速向前合并。当和目标分支之间的路径不是线性之时,Git只能执行 三路合并 。三路合并使用一个专门的提交来合并两个分支的历史。这个术语取自这样一个事实,Git使用 三个 提交来生成合并提交:两个分支顶端和它们共同的祖先。

    如果你尝试合并的两个分支同一个文件的同一个部分,Git将无法决定使用哪个版本。当这种情况发生时,它会停在合并提交,让你手动解决这些冲突。Git的合并流程令人称赞的一点是,它使用我们熟悉的“编辑/缓存/提交”工作流来解决冲突。当你遇到合并冲突时,运行git status命令来查看哪些文件存在需要解决的冲突。

    git merge 

    将指定分支并入当前分支。

    git merge --no-ff 
    

    将指定分支并入当前分支,但 总是 生成一个合并提交(即使是快速向前合并)。这可以用来记录仓库中发生的所有合并。

  • git rebase

    作为merge的替代选择,你可以像下面这样将feature分支并入master分支:

    git checkout feature
    git rebase master
    

    它会把整个feature分支移动到master分支的后面,有效地把所有master分支上新的提交并入过来。但是,rebase为原分支上每一个提交创建一个新的提交,重写了项目历史,并且不会带来合并提交。rebase最大的好处是你的项目历史会非常整洁。首先,它不像git merge 那样引入不必要的合并提交。其次,如上图所示,rebase导致最后的项目历史呈现出完美的线性——你可以从项目终点到起点浏览而不需要任何的Fork。这让你更容易使用git loggit bisectgitk 来查看项目历史。不过,这种简单的提交历史会带来两个后果:安全性和可跟踪性。如果你违反了Rebase黄金法则,重写项目历史可能会给你的协作工作流带来灾难性的影响。此外,rebase不会有合并提交中附带的信息——你看不到feature分支中并入了上游的哪些更改。

    交互式的rebase允许你更改并入新分支的提交。这比自动的rebase更加强大,因为它提供了对分支上提交历史完整的控制。一般来说,这被用于将feature分支并入master分支之前,清理混乱的历史。把-i 传入git rebase 选项来开始一个交互式的rebase过程:

    git checkout feature
    git rebase -i master
    

    它会打开一个文本编辑器,显示所有将被移动的提交:

    pick 33d5b7a Message for commit #1
    pick 9480b3d Message for commit #2
    pick 5c67e61 Message for commit #3
    

    这个列表定义了rebase将被执行后分支会是什么样的。更改pick 命令或者重新排序,这个分支的历史就能如你所愿了。比如说,如果第二个提交修复了第一个提交中的小问题,你可以用fixup 命令把它们合到一个提交中:

    pick 33d5b7a Message for commit #1
    fixup 9480b3d Message for commit #2
    pick 5c67e61 Message for commit #3
    

    保存后关闭文件,Git会根据你的指令来执行rebase,忽略不重要的提交会让你的feature分支的历史更清晰易读。这是git merge 做不到的。

    git rebase 的黄金法则便是,绝不要在公共的分支上使用它。

4.代码同步

  • git remote

    git remote命令允许你创建、查看和删除和其它仓库之间的连接。远程连接更像是书签,而不是直接跳转到其他仓库的链接。它用方便记住的别名引用不那么方便记住的URL,而不是提供其他仓库的实时连接。Git被设计为给每个开发者提供完全隔离的开发环境。也就是说信息并不是自动地在仓库之间传递。开发者需要手动将上游提交拉取到本地,或手动将本地提交推送到中央仓库中去。git remote命令正是将URL传递给这些“共享”命令的快捷方式。当你用git clone克隆仓库时,它自动创建了一个名为origin的远程连接,指向被克隆的仓库。当开发者创建中央仓库的本地副本时非常有用,因为它提供了拉取上游更改和发布本地提交的快捷方式。这也是为什么大多数基于Git的项目将它们的中央仓库取名为origin。Git支持多种方式来引用一个远程仓库。其中两种最简单的方式便是HTTP和SSH协议。HTTP是允许匿名、只读访问仓库的简易方式。比如:

    http://host/path/to/repo.git
    

    但是,直接将提交推送到一个HTTP地址一般是不可行的(你不太可能希望匿名用户也能随意推送)。如果希望对仓库进行读写,你需要使用SSH协议:

    ssh://user@host/path/to/repo.git
    

    你需要在托管的服务器上有一个有效的SSH账户,但不用麻烦了,Git支持开箱即用的SSH认证连接。

    git remote
    

    列出你和其他仓库之间的远程连接。

    git remote -v
    

    和上个命令相同,但同时显示每个连接的URL。

    git remote add  
    

    创建一个新的远程仓库连接。在添加之后,你可以将作为便捷的别名在其他Git命令中使用。

    git remote rm 
    

    移除名为的远程仓库的连接。

    git remote rename  
    

    将远程连接从重命名为。

  • git fetch

    git fetch命令将提交从远程仓库导入到你的本地仓库。拉取下来的提交储存为远程分支,而不是我们一直使用的普通的本地分支。你因此可以在整合进你的项目副本之前查看更改。当你希望查看其他人的工作进展时,你需要fetch。fetch下来的内容表示为一个远程分支,因此不会影响你的本地开发。这是一个安全的方式,在整合进你的本地仓库之前,检查那些提交。类似于svn update,你可以看到中央仓库的历史进展如何,但它不会强制你将这些进展合并入你的仓库。

    git fetch 
    

    拉取仓库中所有的分支。同时会从另一个仓库中下载所有需要的提交和文件。

    git fetch  
    

    和上一个命令相同,但只拉取指定的分支。

  • git pull

    在基于Git的协作工作流中,将上游更改合并到你的本地仓库是一个常见的工作。我们已经知道应该使用git fetch,然后是git merge,但是git pull将这两个命令合二为一。

    git pull 
    

    拉取当前分支对应的远程副本中的更改,并立即并入本地副本。效果和git fetch后接git merge origin/.一致。

    git pull --rebase 
    

    和上一个命令相同,但使用git rebase合并远程分支和本地分支,而不是使用git merge

  • git push

    Push是你将本地仓库中的提交转移到远程仓库中时要做的事。它和git fetch正好相反,fetch将提交导入到本地分支,而push将提交导出到远程分支。它可以覆盖已有的更改,所以你需要小心使用。这些情况请见下面的讨论。

    git push  
    

    将指定的分支推送到上,包括所有需要的提交和提交对象。它会在目标仓库中创建一个本地分支。为了防止你覆盖已有的提交,如果会导致目标仓库非快速向前合并时,Git不允许你push。

    git push  --force
    

    和上一个命令相同,但即使会导致非快速向前合并也强制推送。除非你确定你所做的事,否则不要使用--force标记。

    git push  --all
    

    将所有本地分支推送到指定的远程仓库。

    git push  --tags
    

    当你推送一个分支或是使用--all选项时,标签不会被自动推送上去。--tags将你所有的本地标签推送到远程仓库中去。

    git push最常见的用法是将你的本地更改发布到中央仓库。在你积累了一些本地提交,准备和同事们共享时,你(可以)用交互式rebase来清理你的提交,然后推送到中央仓库去。Git为了防止你覆盖中央仓库的历史,会拒绝你会导致非快速向前合并的推送请求。所以,如果远程历史和你本地历史已经分叉,你需要将远程分支pull下来,在本地合并后再尝试推送。这和SVN让你在提交更改集合之前要和中央仓库同步是类似的。--force这个标记覆盖了这个行为,让远程仓库的分支符合你的本地分支,删除你上次pull之后可能的上游更改。只有当你意识到你刚刚共享的提交不正确,并用git commit --amend或者交互式rebase修复之后,你才需要用到强制推送。但是,你必须绝对确定在你使用--force标记前你的同事们都没有pull这些提交。

5.git日志

  • Oneline

    --oneline标记把每一个提交压缩到了一行中。它默认只显示提交ID和提交信息的第一行。git log --oneline的输出一般是这样的:

    0e25143 Merge branch 'feature'
    ad8621a Fix a bug in the feature
    16b36c6 Add a new feature
    23ad9ad Add the initial code base
    

    它对于获得项目的总体情况很有帮助。

  • Decorate

    很多时候,知道每个提交关联的分支或者标签很有用。--decorate标记让git log显示指向这个提交的所有引用(比如说分支、标签等)。

    这可以和另一个配置项一起使用。比如,执行git log --oneline --decorate 会将提交历史格式化成这样:

    0e25143 (HEAD, master) Merge branch 'feature'
    ad8621a (feature) Fix a bug in the feature
    16b36c6 Add a new feature
    23ad9ad (tag: v0.9) Add the initial code base
    

    在这个例子中,你(通过HEAD标记)可以看到最上面那个提交已经被checkout了,而且它还是master分支的尾端。第二个提交有另一个feature分支指向它,以及最后那个提交带有v0.9标签。

    分支、标签、HEAD还有提交历史是你Git仓库中包含的所有信息。因此,这个命令让你更完整地观察项目结构。

 

  • Diff

    git log提供了很多选项来显示两个提交之间的差异。其中最常用的两个是--stat-p

    --stat选项显示每次提交的文件增删数量(注意:修改一行记作增加一行且删去一行),当你想要查看提交引入的变化时这会非常有用。比如说,下面这个提交在hello.py文件中增加了67行,删去了38行。

    commit f2a238924e89ca1d4947662928218a06d39068c3
    Author: John <john@example.com>
    Date:   Fri Jun 25 17:30:28 2014 -0500
    
        Add a new feature
    
     hello.py | 105 ++++++++++++++++++++++++-----------------
     1 file changed, 67 insertion(+), 38 deletions(-)
    

    文件名后面+和-的数量是这个提交造成的更改中增删的相对比例。它给你一个直观的感觉,关于这次提交有多少改动。如果你想知道每次提交删改的绝对数量,你可以将-p 选项传入git log。这样提交所有的删改都会被输出:

    commit 16b36c697eb2d24302f89aa22d9170dfe609855b
    Author: Mary <mary@example.com>
    Date:   Fri Jun 25 17:31:57 2014 -0500
    
        Fix a bug in the feature
    
    diff --git a/hello.py b/hello.py
    index 18ca709..c673b40 100644
    --- a/hello.py
    +++ b/hello.py
    @@ -13,14 +13,14 @@ B
    -print("Hello, World!")
    +print("Hello, Git!")
    

    对于改动很多的提交来说,这个输出会变得又长又大。一般来说,当你输出所有删改的时候,你是想要查找某一具体的改动,这时你就要用到pickaxe 选项。

  • Shortlog

    git shortlog是一种特殊的git log ,它是为创建发布声明设计的。它把每个提交按作者分类,显示提交信息的第一行。这样可以容易地看到谁做了什么。

    比如说,两个开发者为项目贡献了5个提交,那么git shortlog 输出会是这样的:

    Mary (2):
          Fix a bug in the feature
          Fix a serious security hole in our framework
    
    John (3):
          Add the initial code base
          Add a new feature
          Merge branch 'feature'
    

    默认情况下,git shortlog把输出按作者名字排序,但你可以传入-n选项来按每个作者提交数量排序。

  • Graph

    --graph 选项绘制一个ASCII图像来展示提交历史的分支结构。它经常和 --oneline--decorate两个选项一起使用,这样会更容易查看哪个提交属于哪个分支:

    git log --graph --oneline --decorate
    For a simple repository with just 2 branches, this will produce the following:
    
    *   0e25143 (HEAD, master) Merge branch 'feature'
    |\  
    | * 16b36c6 Fix a bug in the new feature
    | * 23ad9ad Start a new feature
    * | ad8621a Fix a critical security issue
    |/  
    * 400e4b7 Fix typos in the documentation
    * 160e224 Add the initial code base
    

    星号表明这个提交所在的分支,所以上图的意思是23ad9ad16b36c6这两个提交在topic分支上,其余的在master分支上。

    虽然这对简单的项目来说是个很好用的选择,但你可能会更喜欢gitk或SourceTree这些更强大的可视化工具来分析大型项目。

  • 自定义格式

    对于其他的git log格式需求,你都可以使用--pretty=format:"<string>"选项。它允许你使用像printf一样的占位符来输出提交。

    比如,下面命令中的%cn%h%cd这三种占位符会被分别替换为作者名字、缩略标识和提交日期。

    git log --pretty=format:"%cn committed %h on %cd"
    This results in the following format for each commit:
    
    John committed 400e4b7 on Fri Jun 24 12:30:04 2014 -0500
    John committed 89ab2cf on Thu Jun 23 17:09:42 2014 -0500
    Mary committed 180e223 on Wed Jun 22 17:21:19 2014 -0500
    John committed f12ca28 on Wed Jun 22 13:50:31 2014 -0500
    

    完整的占位符清单可以在文档中找到。

    除了让你只看到关注的信息,这个--pretty=format:"<string>" 选项在你想要在另一个命令中使用日志内容是尤为有用的。

  • 过滤提交历史

    格式化提交输出只是git log其中的一个用途。另一半是理解如何浏览整个提交历史。接下来的文章会介绍如何用git log选择项目历史中的特定提交。所有的用法都可以和上面讨论过的格式化选项结合起来。

    1).按数量

    git log最基础的过滤选项是限制显示的提交数量。当你只对最近几次提交感兴趣时,它可以节省你一页一页查看的时间。

    你可以在后面加上-<n>选项。比如说,下面这个命令会显示最新的3次提交:

    git log -3
    
    2).按日期

    如果你想要查看某一特定时间段内的提交,你可以使用--after--before 标记来按日期筛选。它们都接受好几种日期格式作为参数。比如说,下面的命令会显示2014年7月1日后(含)的提交:

    git log --after="2014-7-1"
    

    你也可以传入相对的日期,比如一周前(”1 week ago“)或者昨天(”yesterday“):

    get log --after="yesterday"
    

    你可以同时提供--before--after 来检索两个日期之间的提交。比如,为了显示2014年7月1日到2014年7月4日之间的提交,你可以这么写:

    git log --after="2014-7-1" --before="2014-7-4"
    

    注意--since--until 标记和--after--before标记分别是等价的。

    3).按作者

    当你只想看某一特定作者的提交的时候,你可以使用--author标记。它接受正则表达式,返回所有作者名字满足这个规则的提交。如果你知道那个作者的确切名字你可以直接传入文本字符串:

    git log --author="John"
    

    它会显示所有作者叫John的提交。作者名不一定是全匹配,只要包含那个子串就会匹配。

    你也可以用正则表达式来创建更复杂的检索。比如,下面这个命令检索名叫Mary或John的作者的提交。

    git log --author="John\|Mary"
    

    注意作者的邮箱地址也算作是作者的名字,所以你也可以用这个选项来按邮箱检索。

    如果你的工作流区分提交者和作者,--committer也能以相同的方式使用。

    4).按提交信息

    按提交信息来过滤提交,你可以使用--grep标记。它和上面的--author标记差不多,只不过它搜索的是提交信息而不是作者。

    比如说,你的团队规范要求在提交信息中包括相关的issue编号,你可以用下面这个命令来显示这个issue相关的所有提交:

    git log --grep="JRA-224:"
    

    你也可以传入-i参数来忽略大小写匹配。

    5).按文件

    很多时候,你只对某个特定文件的更改感兴趣。为了显示某个特定文件的历史,你只需要传入文件路径。比如说,下面这个命令返回所有和foo.pybar.py文件相关的提交:

    git log -- foo.py bar.py
    

    --告诉git log接下来的参数是文件路径而不是分支名。如果分支名和文件名不可能冲突,你可以省略--

    6).按内容

    我们还可以根据源代码中某一行的增加和删除来搜索提交。这被称为pickaxe,它接受形如-S"<string>"的参数。比如说,当你想要知道Hello, World!字符串是什么时候加到项目中哪个文件中去的,你可以使用下面这个命令:

    git log -S "Hello, World!"
    

    如果你想用正则表达式而不是字符串来搜索,你可以使用-G"<regex>"标记。

    这是一个非常强大的调试工具,它能让你定位到所有影响代码中特定一行的提交。它甚至可以让你看到某一行是什么时候复制或者移动到另一个文件中去的。

    7).按范围

    你可以传入范围来筛选提交。这个范围由下面这样的格式指定,其中<since><until>是提交的引用:

    git log <since>..<until>
    

    这个命令在你使用分支引用作为参数时特别有用。这是显示两个分支之间区别最简单的方式。看看下面这个命令:

    git log master..feature
    

    其中的master..feature范围包含了在feature分支而不在master分支中所有的提交。换句话说,这个命令可以看出从master分支fork到feature分支后发生了哪些变化。

(以上,git系列全部完结)

 

Advertisements

One thought on “git代码库工作流

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s