git使用初探

CVS及SVN都是集中式的版本控制系统,而Git是分布式版本控制系统,集中式和分布式版本控制系统有什么区别呢?

  • DVCS 通过本地提交支持离线工作,这是由 DVCS 的操作方式决定的。这与集中式版本控制完全不同.

  • 集中式版本控制要求通过到中心服务器的连接执行所有操作。这种灵活性让开发人员在飞机上也能够像在办公室中一样轻松地工作,可以一次又一次地进行提交。

  • 在需要进行推(push )操作(与另一个节点通信)时,速度也更快,因为两个客户机机器上都有完整的元数据。速度差异相当显著,根据使用本地存储库还是网络存储库,DVCS 比Subversion 快大约 3-10 倍。

git 概念

本地仓库由git维护的三棵树组成:

  • 工作目录(workset):持有实际文件
  • 暂存区(index): 缓存区域,临时保存你的改动
  • Head:指向最后一次提交的结果

workset –> add –> index –> commit –>Head

创建仓库

运行下面命令,系统会默认为本地的分支为master远程的分支为origin

  • 创建新仓库,创建新文件夹,打开,然后执行

    git init 
    
  • 从本地的git clone一个仓库

    git clone /path/to/repository
    
  • 连接远程仓库

    git remote add origin <server>
    
  • clone 远程服务器上的仓库

    git clone username@host:/path/to/repository
    

上传修改( add, commit, push)

  • 添加到index中

    git add <filename>
    git add *
    
  • 更新本地head,使用如下命令以实际提交改动:

    git commit -m "代码提交信息"
    git commit -ma "代码提交,并将发生改动的加入到index中"
    
  • 推送改动

    git push origin master # master可以换成我们想要推送的分支
    

更新与合并( pull, merge, checkout, fetch, reset)

  • 要更新本地仓库至最新改动,执行:

    git pull
    
  • 该命令将获取并合并远端的改动,要合并其他分支到我们的分支,执行:

    git merge <branch>
    
  • 放弃本地改动,取用服务器上的版本

    git checkout -- <filename>
    

此命令会使用HEAD中的最新内容替换掉你的工作目录中的文件,已添加到暂存区的改动以及新文件都不会受到影响

例如:在hello.py所在的文件夹下执行,即可

git checkout -- hello.py
  • 如果想丢弃在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将本地分支指向它

    git fetch origin

    git reset –hard origin/master

也可以使用stash命令,放弃所有修:

git stash drop

分支

分支是用来将特性开发绝缘开来的,在创建仓库的时候,master是默认的分支,在其他分支上进行开发,完成后在将它们合并到主分支上。

  • 切换分支:

    git checkout -b feature_x # 创建一个叫做”feature_x“的分支,并切换过去:
    
  • 切换回主分支:

    git checkout master
    
  • 删除分支

    git branch -d feature_x
    
  • 推送分支

    git push origin <branch>
    

合并分支

场景:origin为公司代码的主干版本,而我们团队负责为某个项目特别优化的一个分支版本,但是定期我们需要合并主干版本的重要更新,并且在关键节点上要发布产品。

假设我们现在基于远程分支“origin”创建一个叫“mywork”的分支,origin之前已经有两个提交了:

git checkout -b mywork origin

然后origin有两个更新C3,C4, mywork有两个更新C5,C6

merge

git 进行merge的结果比较容易理解,若执行命令

git checkout mywork
git mrege origin

则会将origin中的修改合并到mywork分支中.

冲突解决

有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起。此时合并会中止,发生冲突的文件会以unmerged状态标识出来。

<<<<<<< HEAD
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
  please contact us at support@github.com
</div>
>>>>>>> iss53

可以看到 ======= 隔开的上半部分,是 HEAD(即 master 分支,在运行 merge 命令时所切换到的分支)中的内容,下半部分是在 iss53 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。比如你可以通过把这段内容替换为下面这样来解决:

<div id="footer">
    please contact us at email.support@github.com
</div>

运行 git add 将把它们标记为已解决状态

git add file

如果你想用一个有图形界面的工具来解决这些问题,不妨运行 git mergetool,它会调用一个可视化的合并工具并引导你解决所有冲突,退出合并工具以后,Git 会询问你合并是否成功。如果回答是,它会为你把相关文件暂存起来,以表明状态为已解决。

就可以用 git commit 来完成这次合并提交。提交的记录差不多是这样:

git commit

更复杂的合并,可以参考 分支合并

rebase

rebase 意为重新使用基础版本,这个命令实际上对应了非常多的操作,mywork本来是origin 分支C2分出去的,origin有C3,C4有两个版本的内核更新,mywork在分出去后又C5,C6两个版本外观更新,这个时候使用rebase可以先把C5,C6当成补丁暂存,先把内核更新到origin的C4,然后再将C5,C6以补丁的方式更新上来,这样看起来的mywork的更新的图就是下面这样。

运行gc命令的话,C5,C6的commit就会被删除回收掉。

在rebase的过程中,也许会出现冲突(conflict). 在这种情况,Git会停止rebase并会让你去解决 冲突;在解决完冲突后,用”git-add”命令去更新这些内容的索引(index), 然后,你无需执行 git-commit,只要执行:

$ git rebase --continue

在任何时候,你可以用–abort参数来终止rebase的行动,并且”mywork” 分支会回到rebase开始前的状态。

$ git rebase --abort

git rebase和git merge的区别:

当我们使用Git log来参看commit时,其commit的顺序也有所不同。
假设C3提交于9:00AM,C5提交于10:00AM,C4提交于11:00AM,C6提交于12:00AM,
对于使用git merge来合并所看到的commit的顺序(从新到旧)是:C7 ,C6,C4,C5,C3,C2,C1(严格按照时间顺序排列)
对于使用git rebase来合并所看到的commit的顺序(从新到旧)是:C7 ,C6‘,C5’,C4,C3,C2,C1
因为C6’提交只是C6提交的克隆,C5’提交只是C5提交的克隆,
从用户的角度看使用git rebase来合并后所看到的commit的顺序(从新到旧)是:C7 ,C6,C5,C4,C3,C2,C1

标签(TODO)

为软件发布创建标签是推荐的,这个概念早已存在,在SVN中就有有,可以执行如下命令创建一个叫做1.0.0的标签:

git tag 1.0.0 1b2e1d63ff

1b231d63ff是我们想要标记的提交ID的前10位字符,可以使用如下命令获取提交ID:

git log

也可以使用少一点的提交ID前几位,只要它的指向具有唯一性。