Git 版本控制学习笔记

目录

git 项目协同开发模式

搭建自己的git 服务器

多人协作

Git命令技巧

版本回退

查看远程分支

删除远程分支和tag

删除tag

删除不存在对应远程分支的本地分支

重命名远程分支

把本地tag推送到远程

获取远程tag

Git中的子模块

新建空白分支


git 项目协同开发模式

利用git进行项目协同开发的模式 https://github.com/livoras/blog/issues/7

Git 版本控制学习笔记_第1张图片

 

在项目的开始到结束,我们会有两种仓库。一种是源仓库(origin),一种是开发者仓库。上图中的每个矩形都表示一个仓库,正中间的是我们的源仓库,而其他围绕着源仓库的则是开发者仓库。

源仓库

在项目的开始,项目的发起者构建起一个项目的最原始的仓库,我们把它称为origin源仓库的有两个作用:

  1. 汇总参与该项目的各个开发者的代码
  2. 存放趋于稳定和可发布的代码

源仓库应该是受保护的,开发者不应该直接对其进行开发工作。只有项目管理者(通常是项目发起人)能对其进行较高权限的操作。

开发者仓库

上面说过,任何开发者都不会对源仓库进行直接的操作,源仓库建立以后,每个开发者需要做的事情就是把源仓库的复制一份,作为自己日常开发的仓库。这个复制,也就是github上面的fork

每个开发者所fork的仓库是完全独立的,互不干扰,甚至与源仓库都无关。每个开发者仓库相当于一个源仓库实体的影像,开发者在这个影像中进行编码,提交到自己的仓库中,这样就可以轻易地实现团队成员之间的并行开发工作。而开发工作完成以后,开发者可以向源仓库发送pull request,请求管理员把自己的代码合并到源仓库中,这样就实现了分布式开发工作,和最后的集中式的管理。

分支(Branch

在文章开头的那张图中,每一个矩形内部纷繁的枝蔓便是git的分支模型。可以看出,每个开发者的仓库都有自己的分支路线,而这些分支路线会通过代码汇总映射到源仓库中去。

我们为git定下一种分支模型,在这种模型中,分支有两类,五种

  • 永久性分支
    • master branch:主分支
    • develop branch开发分支
  • 临时性分支
    • feature branch:功能分支
    • release branch:预发布分支
    • hotfix branchbug修复分支

永久性分支

永久性分支是寿命无限的分支,存在于整个项目的开始、开发、迭代、终止过程中。永久性分支只有两个masterdevelop

master主分支从项目一开始便存在,它用于存放经过测试,已经完全稳定代码;在项目开发以后的任何时刻当中,master存放的代码应该是可作为产品供用户使用的代码。所以,应该随时保持master仓库代码的清洁和稳定,确保入库之前是通过完全测试和代码reivew的。master分支是所有分支中最不活跃的,大概每个月或每两个月更新一次,每一次master更新的时候都应该用git打上tag,说明你的产品有新版本发布了。

develop开发分支,一开始从master分支中分离出来,用于开发者存放基本稳定代码。之前说过,每个开发者的仓库相当于源仓库的一个镜像,每个开发者自己的仓库上也有masterdevelop。开发者把功能做好以后,是存放到自己的develop中,当测试完以后,可以向管理者发起一个pull request,请求把自己仓库的develop分支合并到源仓库的develop中。

所有开发者开发好的功能会在源仓库的develop分支中进行汇总,当develop的代码经过不断的测试,已经逐渐趋于稳定了,接近产品目标了。这时候,我们就可以把develop分支合并到master分支中,发布一个新版本。所以,一个产品不断完善和发布过程就正如下图:

Git 版本控制学习笔记_第2张图片

暂时性分支

暂时性分支和永久性分支不同,暂时性分支在开发过程中是一定会被删除的。所有暂时性分支,一般源于develop,最终也一定会回归合并到develop

feature功能性分支,是用于开发项目的功能的分支,是开发者主要战斗阵地。开发者在本地仓库从develop分支分出功能分支,在该分支上进行功能的开发,开发完成以后再合并到develop支上,这时候功能性分支已经完成任务,可以删除。功能性分支的命名一般为feature-**为需要开发的功能的名称。

Git 版本控制学习笔记_第3张图片

例子


假设我是一名PingHackers网站的开发者,已经把源仓库fork了,并且clone到了本地。现在要开发PingHackers网站的讨论功能。我在本地仓库中可以这样做:

step 1: 切换到develop分支

        >>> git checkout develop

step 2: 分出一个功能性分支

        >>> git checkout -b feature-discuss

step 3: 在功能性分支上进行开发工作,多次commit,测试以后...

step 4: 把做好的功能合并到develop

               >>> git checkout develop

              # 回到develop分支

 

              >>> git merge --no-ff feature-discuss

             # 把做好的功能合并到develop

 

             >>> git branch -d feature-discuss

             # 删除功能性分支

 

             >>> git push origin develop

             # develop提交到自己的远程仓库中

 

这样,就完成一次功能的开发和提交。


release预发布分支,当产品即将发布的时候,要进行最后的调整和测试,这时候就可以分出一个预发布分支,进行最后的bug fix。测试完全以后,发布新版本,就可以把预发布分支删除。预发布分支一般命名为release-*

hotfix修复bug分支,当产品已经发布了,突然出现了重大的bug。这时候就要新建一个hotfix分支,继续紧急的bug修复工作,当bug修复完以后,把该分支合并到masterdevelop以后,就可以把该分支删除。修复bug分支命名一般为hotfix-*

releasehotfix分支离我们还比较遥远。。就不详述,有兴趣的同学可以参考本文最后的参考资料进行学习。


搭建自己的git 服务器

创建远程仓库: git init --bare XXX.git

将已有代码上传:1)git init,2)git remote add origin git_address/XXX.git ( 添加远程仓库master路径),3)git add . (把当前路径下所有内容上传),4)git commit -m "comment",5)git push -u origin master (第一次上传添加-u)

开发过程中,将分支切换到develop 分支,git checkou -b develop


多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin

要查看远程库的信息,用git remote

    $ git remote origin

或者,用git remote -v显示更详细的信息:

            $ git remote -v

              origin git@github.com:michaelliao/learngit.git (fetch)

              origin git@github.com:michaelliao/learngit.git (push)

        上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。

 

推送分支

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:

    $ git push origin master

如果要推送其他分支,比如dev,就改成:

    $ git push origin dev

 

但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?

  • master分支是主分支,因此要时刻与远程同步;
  • dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
  • bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug
  • feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!

 

抓取分支

多人协作时,大家都会往masterdev分支上推送各自的修改。

现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆:

$ git clone git@github.com:michaelliao/learngit.git

      Cloning into 'learngit'... remote: Counting objects: 46, done.

      remote: Compressing objects: 100% (26/26), done.

      remote: Total 46 (delta 16), reused 45 (delta 15)

      Receiving objects: 100% (46/46), 15.69 KiB | 6 KiB/s, done.

      Resolving deltas: 100% (16/16), done.

当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。可以用git branch命令看看:

$ git branch

       * master

 

现在,你的小伙伴要在dev分支上开发,就必须创建远程origindev分支到本地,于是他用这个命令创建本地dev分支:

$ git checkout -b dev origin/dev(已经存在,git checkout dev)

现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程:

$ git commit -m "add /usr/bin/env"

       [dev 291bea8] add /usr/bin/env

       1 file changed, 1 insertion(+)

 

$ git push origin dev

       Counting objects: 5, done.

       Delta compression using up to 4 threads.

       Compressing objects: 100% (2/2), done.

       Writing objects: 100% (3/3), 349 bytes, done.

       Total 3 (delta 0), reused 0 (delta 0)

       To [email protected]:michaelliao/learngit.git

       fc38031..291bea8 dev -> dev

 

你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:

$ git add hello.py

 

$ git commit -m "add coding: utf-8"

      [dev bd6ae48] add coding: utf-8

      1 file changed, 1 insertion(+)

 

$ git push origin dev

      To [email protected]:michaelliao/learngit.git

      ! [rejected] dev -> dev (non-fast-forward)

      error: failed to push some refs to '[email protected]:michaelliao/learngit.git'

      hint: Updates were rejected because the tip of your current branch is behind

      hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')

      hint: before pushing again.

      hint: See the 'Note about fast-forwards' in 'git push --help' for details.

推送失败

推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:

$ git pull

       remote: Counting objects: 5, done.

       remote: Compressing objects: 100% (2/2), done.

       remote: Total 3 (delta 0), reused 3 (delta 0) Unpacking objects: 100% (3/3), done.

       From github.com:michaelliao/learngit

             fc38031..291bea8 dev -> origin/dev

       There is no tracking information for the current branch.

       Please specify which branch you want to merge with.

       See git-pull(1) for details

             git pull

        If you wish to set tracking information for this branch you can do so with:

             git branch --set-upstream dev origin/

git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置devorigin/dev的链接:

$ git branch --set-upstream dev origin/dev (或执行git checkout dev)

        Branch dev set up to track remote branch dev from origin.

pull

$ git pull

        Auto-merging hello.py

        CONFLICT (content):

        Merge conflict in hello.py

        Automatic merge failed; fix conflicts and then commit the result.

这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。

 

解决后,提交,再push

$ git commit -m "merge & fix hello.py"

         [dev adca45d] merge & fix hello.py

$ git push origin dev

         Counting objects: 10, done.

         Delta compression using up to 4 threads.

         Compressing objects: 100% (5/5), done.

         Writing objects: 100% (6/6), 747 bytes, done.

         Total 6 (delta 0), reused 0 (delta 0)

         To [email protected]:michaelliao/learngit.git

                291bea8..adca45d dev -> dev

因此,多人协作的工作模式通常是这样:

  1. 首先,可以试图用git push origin branch-name推送自己的修改;
  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
  3. 如果合并有冲突,则解决冲突,并在本地提交;
  4. 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!

如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git checkout branch-name

这就是多人协作的工作模式,一旦熟悉了,就非常简单。

 

小结

  • 查看远程库信息,使用git remote -v
  • 本地新建的分支如果不推送到远程,对其他人就是不可见的;
  • 从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;
  • 在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;(命令错误)
  • 建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name
  • 从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。

Git命令技巧

版本回退

git log命令显示从最近到最远的提交日志,如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline参数:

看到的一大串类似3628164...882e1e0的是commit id(版本号),和SVN不一样,Gitcommit id不是123……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id和我的肯定不一样,以你自己的为准。为什么commit id需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用123……作为版本号,那肯定就冲突了。

每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线。

版本回退命令:

1) git reset --hard HEAD^

首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是最新的提交3628164...882e1e0(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100^比较容易数不过来,所以写成HEAD~100

2) git reset --hard commit_id(不必全写,可分辨即可)

 

版本回退再回来:

Git提供了一个命令git reflog用来记录你的每一次命令,可以查看commit_id,然后使用git reset

现在总结一下:

  • HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id
  • 穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
  • 要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。

 

查看远程分支

加上-a参数可以查看远程分支,远程分支会用红色表示出来(如果你开了颜色支持的话):

$ git branch -a

 

删除远程分支和tag

在Git v1.7.0 之后,可以使用这种语法删除远程分支

$ git push origin --delete

 

删除tag

git push origin --delete tag

否则,可以使用这种语法,推送一个空分支到远程分支,其实就相当于删除远程分支

git push origin :

这是删除tag的方法,推送一个空tag到远程tag:

git tag -d

git push origin :refs/tags/

 

两种语法作用完全相同。

 

删除不存在对应远程分支的本地分支

假设这样一种情况:

我创建了本地分支b1并pull到远程分支 origin/b1;

其他人在本地使用fetch或pull创建了本地的b1分支;

我删除了 origin/b1 远程分支;

其他人再次执行fetch或者pull并不会删除这个他们本地的 b1 分支,运行 git branch -a 也不能看出这个branch被删除了,如何处理?

使用下面的代码查看b1的状态:

$ git remote show origin

       * remote origin

       Fetch URL: [email protected]:xxx/xxx.git

       Push URL: [email protected]:xxx/xxx.git

       HEAD branch: master

       Remote branches:

              master tracked

              refs/remotes/origin/b1 stale (use 'git remote prune' to remove)

       Local branch configured for 'git pull':

              master merges with remote master

       Local ref configured for 'git push':

              master pushes to master (up to date)

这时候能够看到b1是stale的,使用 git remote prune origin 可以将其从本地版本库中去除。

更简单的方法是使用这个命令,它在fetch之后删除掉没有与远程分支对应的本地分支:

$ git fetch -p

重命名远程分支

在git中重命名远程分支,其实就是先删除远程分支,然后重命名本地分支,再重新提交一个远程分支。

例如下面的例子中,我需要把 devel 分支重命名为 develop 分支:

$ git branch -av

       * devel 752bb84 Merge pull request #158 from Gwill/devel

       master 53b27b8 Merge pull request #138 from tdlrobin/master

       zrong 2ae98d8 modify CCFileUtils, export getFileData

       remotes/origin/HEAD -> origin/master

       remotes/origin/add_build_script d4a8c4f Merge branch 'master' into add_build_script

       remotes/origin/devel 752bb84 Merge pull request #158 from Gwill/devel

       remotes/origin/devel_qt51 62208f1 update .gitignore

       remotes/origin/master 53b27b8 Merge pull request #138 from tdlrobin/master

       remotes/origin/zrong 2ae98d8 modify CCFileUtils, export getFileData

删除远程分支:

$ git push --delete origin devel

       To [email protected]:zrong/quick-cocos2d-x.git

       - [deleted] devel

重命名本地分支:

$ git branch -m devel develop

推送本地分支:

$ git push origin develop

     Counting objects: 92, done.

     Delta compression using up to 4 threads.

     Compressing objects: 100% (48/48), done.

     Writing objects: 100% (58/58), 1.38 MiB, done.

     Total 58 (delta 34), reused 12 (delta 5)

     To [email protected]:zrong/quick-cocos2d-x.git

          * [new branch] develop -> develop

然而,在 github 上操作的时候,我在删除远程分支时碰到这个错误

$ git push --delete origin devel

     remote: error: refusing to delete the current branch: refs/heads/devel

     To [email protected]:zrong/quick-cocos2d-x.git

     ! [remote rejected] devel (deletion of the current branch prohibited)

     error: failed to push some refs to '[email protected]:zrong/quick-cocos2d-x.git'

这是由于在 github 中,devel 是项目的默认分支。要解决此问题,这样操作:

     进入 github 中该项目的 Settings 页面;

     设置 Default Branch 为其他的分支(例如 master);

     重新执行删除远程分支命令。

把本地tag推送到远程

$ git push --tags

获取远程tag

$ git fetch origin tag


Git中的子模块

参考:Git 工具 - 子模块


新建空白分支

创建分支

使用 git checkout的--orphan参数:

       $ git checkout --orphan branch_name

该命令会创建一个名为branch_name的分支,并且该分支下有前一个分支下的所有文件。

查看--orphan的帮助:

       Create a new orphan branch, named , started from and switch to it. The first commit made on the new branch will have no parents and it will be the root of a new history totally disconnected from all the other branchs and commits.

这里的start point指的是你执行git checkout命令时的那个分支,当然新的分支不会指向任何以前的提交,就是它没有历史,如果你提交当前内容,那么这次提交就是这个分支的首次提交。

删除所有内容

我们不想提交任何内容,所以我们需要把当前内容全部删除,用git命令:

     $ git rm -rf .

提交分支

使用commit命令来提交分支

     $ git commit -am "new branch for documentation"

如果没有任何文件提交的话,分支是看不到的,可以创建一个新文件后再次提交则新创建的branch就会显示出来。

查看是否成功

使用branch来查看分支是否创建成功

     $ git branch -a

 

你可能感兴趣的:(Git 版本控制学习笔记)