- 之前写了一篇 走进git时代一之你该怎么玩? , 主要是对git的特性做了个引导, 用户还是需要自己找资料系统的去学习。
- 所以补充一篇分享,稍微详细一点解释一下svn和git的差异性, 以及日常工作的命令对比 。 帮助大家在学习git的道路上更加清晰。
目录
- 再谈SVN/GIT思想差别带来的影响
- 你需要知道的SVN/GIT命令操作
- 首先还是要强调一下 : 在开始学习 Git 的时候,请不要尝试把各种概念和其他版本控制系统(SVN,P4等)相比拟,否则容易混淆每个操作的实际意义。
- Git 在保存和处理各种信息的时候,虽然操作起来的命令形式非常相近,但它与其他版本控制系统的做法颇为不同。理解这些差异将有助于你准确地使用 Git 提供的各种工具。
文件存储方式不同带来的核心差异
- 系列一的文章中已经提及过,并且给大家看过下面这两张图片,为什么还拿出来, 因为确实一定要强调,大多数其他系统(CVS,Subversion,Perforce,Bazaar 等等)只关心文件内容的具体差异。这类系统每次版本记录有哪些文件作了更新,以及都更新了哪些行的什么内容 。
- 那带来的一个最大的问题就是 : SVN让你无论如何不能重构代码的树冲突
树冲突,指的是由于目录(文件)树的改变,造成内容修改修改不能匹配在同一对象(目录/文件)上。 当一名开发人员移动、重命名、删除一个文件或文件夹,而另一名开发人员也对它们进行了移动、重命名、删除或者仅仅是修改时就会发生树冲突。
- 例如:由于在一个分支中修改的目录和文件,在另外的分支出现了改名的操作。
或因为两个分支同时增加了一个同名的目录,导致了树冲突。
- 阿里巴巴内部的SCM同学花了巨大的精力去解决树冲突,还申请了一个专利……
- 在业内,SVN的树冲突也是一直难以解决的问题,比如这个帖子,看完之后都觉得残忍: http://www.oschina.net/question/103087_12309
再说Git : Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,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 对象。
- 作些修改后再次提交,那么这次的提交对象会包含一个指向上次提交对象的指针(即下图中的 parent 对象)。两次提交后,仓库历史会变成下面的样子
- 所以,Git 中的分支:
其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。
- 在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。
Git 分支的变化
- 再举一个例子: 假设当前的代码分支情况如图所示,当前,我们在 master 分支工作,HEAD 指向着当前的 master 分支。
- HEAD 文件是一个指向你当前所在分支的引用标识符。这样的引用标识符——它看起来并不像一个普通的引用——其实并不包含 SHA-1 值,而是一个指向另外一个引用的指针。
- 执行 git checkout testing 转换到 testing 分支。这时变化如下图,HEAD 指向了 testing 分支。
修改文件并做一次提交:
echo aaa >> test.txt
git commit -a -m 'made change'
- 提交后发生的变化如下图所示,现在 testing 分支向前移动了一格,而 master 分支仍然指向原先 git checkout 时所在的 commit 对象。
- 使用
git checkout master
命令回到 master 分支,效果如下图所示。 - 这条命令做了两件事:它把 HEAD 指针移回到 master 分支,并把工作目录中的文件换成了 master 分支所指向的快照内容。也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。
你需要知道的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 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 缓存区的概念。 系列一文章里讲过 缓存区和 文件的三种状态的概念, 简单就像下面图中所示:
- 平常工作的正常流程是: 修改本地文件,状态从上一次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
- 例如要做如图中的操作:
创建一个叫做“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 操作方面 也告一段落, 以后再写就是协作方面的了。