《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析

  • 之前写了一篇 走进git时代一之你该怎么玩? , 主要是对git的特性做了个引导, 用户还是需要自己找资料系统的去学习。
  • 所以补充一篇分享,稍微详细一点解释一下svn和git的差异性, 以及日常工作的命令对比 。 帮助大家在学习git的道路上更加清晰。

目录


  1. 再谈SVN/GIT思想差别带来的影响
  2. 你需要知道的SVN/GIT命令操作

  • 首先还是要强调一下 : 在开始学习 Git 的时候,请不要尝试把各种概念和其他版本控制系统(SVN,P4等)相比拟,否则容易混淆每个操作的实际意义。
  • Git 在保存和处理各种信息的时候,虽然操作起来的命令形式非常相近,但它与其他版本控制系统的做法颇为不同。理解这些差异将有助于你准确地使用 Git 提供的各种工具。

文件存储方式不同带来的核心差异

  • 系列一的文章中已经提及过,并且给大家看过下面这两张图片,为什么还拿出来, 因为确实一定要强调,大多数其他系统(CVS,Subversion,Perforce,Bazaar 等等)只关心文件内容的具体差异。这类系统每次版本记录有哪些文件作了更新,以及都更新了哪些行的什么内容 。
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 那带来的一个最大的问题就是 : SVN让你无论如何不能重构代码的树冲突
  • 树冲突,指的是由于目录(文件)树的改变,造成内容修改修改不能匹配在同一对象(目录/文件)上。 当一名开发人员移动、重命名、删除一个文件或文件夹,而另一名开发人员也对它们进行了移动、重命名、删除或者仅仅是修改时就会发生树冲突。

    • 例如:由于在一个分支中修改的目录和文件,在另外的分支出现了改名的操作。
    • 或因为两个分支同时增加了一个同名的目录,导致了树冲突。

  • 阿里巴巴内部的SCM同学花了巨大的精力去解决树冲突,还申请了一个专利……
  • 在业内,SVN的树冲突也是一直难以解决的问题,比如这个帖子,看完之后都觉得残忍: http://www.oschina.net/question/103087_12309

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

  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 好处:除了完美解决了树冲突以外, 后面会讲到的强大的分支概念也是源于这样的思想。

Git 另外两个特性

  • 时刻保持数据完整性

    • 在保存到 Git 之前,所有数据都要进行内容的校验和(checksum)计算,并将此结果作为数据的唯一标识和索引。所以如果文件在传输时变得不完整,或者磁盘损坏导致文件数据缺失,Git 都能立即察觉。
    • Git 使用 SHA-1 算法计算数据的校验和,通过对文件的内容或目录的结构计算出一个 SHA-1 哈希值,作为指纹字符串。所有保存在 Git 数据库中的东西都是用此哈希值来作索引的,而不是靠文件名。
  • 多数操作仅添加数据

    • 这种高可靠性令我们的开发工作安心不少,尽管去做各种试验性的尝试好了,再怎样也不会弄丢数据。所以又被称作无线后悔药的版本管理系统。

再详说Git 分支

  • 有人把 Git 的分支模型称为“必杀技特性”,而正是因为它,将 Git 从版本控制系统家族里区分出来。
  • Git 的分支可谓是难以置信的轻量级,它的新建操作几乎可以在瞬间完成,并且在不同分支间切换起来也差不多一样快。 Why ?
  • 之前已经提过,Git 保存的不是文件差异或者变化量,而只是一系列文件快照。在 Git 中提交时,会保存一个提交(commit)对象,该对象包含一个指向暂存内容快照的指针,包含本次提交的作者等相关附属信息。
  • 举个栗子: 假设本地有3个文件,当使用git commit新建一个提交之前,Git 会先计算每一个子目录的校验和,然后在 Git 仓库中将这些目录保存为树(tree)对象。之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。
  • 现在git 仓库中有五个对象(如下图所示):三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 作些修改后再次提交,那么这次的提交对象会包含一个指向上次提交对象的指针(即下图中的 parent 对象)。两次提交后,仓库历史会变成下面的样子
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 所以,Git 中的分支:其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。
  • 在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析

Git 分支的变化

  • 再举一个例子: 假设当前的代码分支情况如图所示,当前,我们在 master 分支工作,HEAD 指向着当前的 master 分支。
  • HEAD 文件是一个指向你当前所在分支的引用标识符。这样的引用标识符——它看起来并不像一个普通的引用——其实并不包含 SHA-1 值,而是一个指向另外一个引用的指针。
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 执行 git checkout testing 转换到 testing 分支。这时变化如下图,HEAD 指向了 testing 分支。
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 修改文件并做一次提交:

    • echo aaa >> test.txt
    • git commit -a -m 'made change'
  • 提交后发生的变化如下图所示,现在 testing 分支向前移动了一格,而 master 分支仍然指向原先 git checkout 时所在的 commit 对象。
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 使用 git checkout master 命令回到 master 分支,效果如下图所示。
  • 这条命令做了两件事:它把 HEAD 指针移回到 master 分支,并把工作目录中的文件换成了 master 分支所指向的快照内容。也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析

你需要知道的SVN/GIT命令操作

  • 强调:我是非常不推荐把SVN和GIT的命令做对比参考的,因为很多命令因为原理不同背后的操作是完全不一样的。 但是如果这个对svn 转git 操作学习有点点帮助,还是列出来吧 ~~

| 操作| GIT | SVN |
| --- | --- | --- |
|检出/复制/克隆 | git clone | svn checkout|
|提交| git commit |svn commit|
|查看提交的详细记录 |git show |svn cat|
|确认状态| git status |svn status|
|确认差异 |git diff |svn diff|
|确认记录| git log |svn log|
|添加 |git add |svn add|
|移动| git mv| svn mv|
|删除 |git rm| svn rm|
|取消修改 |git checkout / git reset| svn revert |
|创建分支| git branch| svn copy |
|切换分支| git checkout| svn switch|
|合并 |git merge| svn merge
|创建标签| git tag| svn copy |
|更新| git pull / git fetch| svn update|
|反映到远端 |git push |svn commit |
|忽略档案目录 |.gitignore |.svnignore|

  • 注意: 刚刚已经讲过,SVN的revert是用来取消修改,但Git的revert是用来消除提交。
  • 以及svn commit 会直接和*仓库交互, svn branch/tag的构造是一样的,git的branch/tag是两回事。

下面开始对场景需要的命令逐一解释


设置与创建新仓库

  • git 的全局设置

    • git config --global user.name “your_name"
    • git config --global user.email “your@email.com"
  • 常用快捷键设置:

    • git config --global alias.st status
    • git config --global alias.co checkout
    • git config --global alias.br branch
    • git config --global alias.ci commit
    • git config --global color.ui true //打开颜色开关
  • 稍微有点意思的配置:
  • git config --global alias.clog "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative"
  • 配置后执行效果如下图:
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 创建新仓库:git init 以创建新的 git 仓库。
  • 检出仓库:

    • git clone git@code.aliyun.com:group/project.git
    • git clone http://code.aliyun.com/group/project.git
    • svn checkout http://code.taobao.org/repos/repo

添加和提交

  • Git:

    • 你可以提出更改(把它们添加到暂存区),使用如下命令:
    • git add <filename>
    • git add .
    • 使用如下命令以实际提交改动:
    • git commit -m "代码提交信息"
    • 你的改动已经提交到了 HEAD,但是还没到你的远端仓库。
  • SVN:

    • 新增文件,svn也需要
    • svn add
    • 但是修改已有文件, 直接通过
    • svn commit -m “代码提交信息"
    • 就直接将变化和版本提交到了远端中心仓库。

Git revert 和 reset

  • 添加和提交这里要强调下git 缓存区的概念。 系列一文章里讲过 缓存区和 文件的三种状态的概念, 简单就像下面图中所示:
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 平常工作的正常流程是: 修改本地文件,状态从上一次commit 变为修改状态,通过git stash 或 git add 放到缓存区, 再通过git commit将修改进入版本库。 当然你也可以跳过缓存区,直接commit -a
  • 如果你把缓存区的文件又进行了编辑修改, 这个文件的内容就会一部分被缓存,当然可以通过git checkout -f,撤销第二次的修改,也可以git add 将第二次的修改也加入到缓存区内,再进行commit 。
  • 当然如果你想撤销你的修改,可以通过git reset 或 git revert ,但当你的commit已经push到远端,被别人pull了下来, 再reset push 的话,别人再pull 就会出现错误,因为这个commit 节点回退到了你本地的缓存区,不在版本系统内,会很麻烦。
  • 所以这种情况下需要使用 git revert ,它是撤销某次操作,此次操作之前和之后的commit和history都会保留,并且把这次撤销作为一次最新的提交。
  • 是提交一个新的版本,将需要revert的版本的内容再反向修改回去,版本会递增,不影响之前提交的内容,别人pull的时候不会出问题,这个很重要。

改动和推送

  • 你的改动现在已经在本地仓库的 HEAD 中了。执行如下命令以将这些改动提交到远端仓库:

    • git push origin master //可以把 master 换成你想要推送的任何分支。
  • 如果你还没有克隆现有仓库,并想将你的仓库连接到某个远程服务器,你可以使用如下命令添加:

    • git remote add origin git@code.aliyun.com:group/project.git
  • 如果要添加多个远程服务器 ,那么:

    • git remote add github git@github.com:group/project.git
  • 那么,在需要推送到另外的远端仓库则:

    • git push github master
  • SVN 没有推送的概念, svn commit 则直接将版本提交到*仓库。

创建分支 — Git

  • 例如要做如图中的操作:
  • 《走进git时代系列三》详解部分git思想及SVN/GIT命令对比解析
  • 创建一个叫做“feature_x”的分支并切换过去:

    • git checkout -b feature_x
  • 切换回主分支:

    • git checkout master
  • 合并分支到主分支,再把新建的分支删掉:

    • git merge feature_x ; git branch -d feature_x

创建分支 — SVN

  • SVN中不存在本地创建分支,合并,推送的这些概念,所以当svn创建分支时,一般是这两种做法:

    • svn copy trunk branches/my-feature_x-branch
    • svn commit -m "Creating a branch"
  • 或者直接远程操作:

    • svn copy http://code.taobao.org/repos/test/trunk http://code.taobao.org/repos/test/branches/my-feature_x-branch -m "Creating a branch"
  • 当需要切换分支时,svn有类似的概念:

    • svn switch http://目录全路径 本地目录全路径
  • 这个命令会更新你的工作副本,映射到一个新的URL,其行为跟“svn update”很像,也会将服务器上文件与本地文件合并。

更新与合并 — Git

  • 要更新你的本地仓库,执行:

    • git pull origin <branch>
  • 这个命令会在你的工作目录中 获取(fetch) 并 合并(merge) 远端的改动。
  • 由于pull 会直接进行merge ,所以建议先进行 git fetch -p ,获取远端的变化,再进行merge
  • 要合并其他分支到你的当前分支(例如 master),执行:

    • git merge <branch>
  • 在这两种情况下,git 都会尝试去自动合并改动。遗憾的是,这可能并非每次都成功,并可能出现冲突(conflicts)。 这时候就需要你修改这些文件来手动合并这些冲突(conflicts)。改完之后,你需要执行如下命令以将它们标记为合并成功:

    • git add <filename>
  • 在合并改动之前,你可以使用如下命令预览差异:

    • git diff <source_branch> <target_branch>
  • 合并区分fast-forward 和 no-fast-forward , 在系列1文章中已经讲到。

更新与合并 -- SVN

  • 需要更新时:

    • svn update
    • svn up
  • 当svn进行合并时一般的做法是:

    • svn merge -r m:n path
    • svn merge branchA branchB
  • 当出现冲突时:

    • svn revert file // 撤销修改
  • 当冲突解决后:

    • svn resolved //resolved命令除了删除冲突文件,还修正了一些记录在工作拷贝管理区域的记录数据

标签

  • 为软件发布创建标签是推荐的。你可以执行如下命令创建一个叫做 1.0.0 的标签:

    • git tag 1.0.0 1b2e1d63ff
  • 1b2e1d63ff 是你想要标记的提交 ID 的前 10 位字符。可以使用下列命令获取提交 ID:

    • git log
  • 你也可以使用少一点的提交 ID 前几位,只要它的指向具有唯一性。 但我们经常会在最新的commit上打标签,则直接运行git tag 1.0.0 即可。
  • SVN的标签和他的分支是同样的概念, 一样是通过svn copy , 例如:

    • svn copy trunk tags/1.0.0
    • svn commit -m "Creating a tag"

替换本地改动 (reset和revert 刚才已经讲过)

  • 假如你操作失误(当然,这最好永远不要发生),你可以使用如下命令替换掉本地改动:

    • git checkout -- <filename>
  • 此命令会使用 HEAD 中的最新内容替换掉你的工作目录中的文件。已添加到暂存区的改动以及新文件都不会受到影响。
  • 假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它:

    -  ` git fetch origin ` 
    -  ` git reset --hard origin/master ` 
    

Git需要特别知道的

  • 春节前 info 公众号推送了一篇 你需要知道的12个Git高级命令
  • 这里面其实最关键的就是:

    • 缓存区,Stash 的运用,
    • reset 和 revert的差别
    • merge,rebase, check-pick 的差别和运用
  • 这三个在本文和 git系列1 都有提及到 。

SVN需要特别知道的

  • SVN 相比git使用上来说, 只有一点(我认为也不是版本管理系统应该保护的点) 优势,就是悲观锁。
  • 当commit 时或者直接通过lock/unlock 命令对某一个文件加锁:

    • svn lock -m "LockMessage" [--force] PATH
    • svn unlock PATH
  • 如果这个文件是二进制文件或者非代码文件(PPT什么的), 这个锁能够减少多人同时修改一个文件,而这个文件又无法diff的代价。
  • 如果多人同时修改并提交二进制文件, 对版本库是有较大的成本的,但是二进制文件不应该进入到代码管理系统内。

写在最后

  • 其实本文很多内容都是直接从《pro git》 这本书里摘的, 愿意系统学习的同学还是要看这本书 。
  • 其他内容感觉日常基本工作也就这些操作了, git 操作方面 也告一段落, 以后再写就是协作方面的了。
上一篇:Ansible简单安装


下一篇:MySQL的字符串函数(十一)下