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

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

目录


  1. 再谈SVN/GIT思想差别带来的影响
  2. 你需要知道的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 操作方面 也告一段落, 以后再写就是协作方面的了。
时间: 2024-09-10 13:26:34

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

《走进git时代系列一》 你该怎么玩?

首先,这篇分享不是git命令操作大全,不是某代码托管服务的硬广, 只是希望激发仍然在使用中世纪时期版本管理系统的同学们,能够放弃你手里的SVN,转向更先进的思路. 所以,大家不会看到非常多的Command Line 教你Step By Step Git init :) , 请放心像读故事一样,慢慢理解为什么要拥抱Git,以及玩转Git你需要做什么? 本文大概分为以下几个篇章: 版本管理的发展历史 为什么Git能如此的火爆 不要用SVN去思考Git 玩转Git与协作 学习Git的一些参考资料 版本

《走进git时代系列二》 从SVN迁移到GIT教程

本篇文章是走进git时代系列之二,如何迁移到GIT的教程, 不了解GIT的同学可以先看系列一<走进git时代系列一> 你该怎么玩? 本文分为以下几部分内容: SVN 迁移到 Git 的简单原理 图文教程从TaoCode SVN 迁移到 YunCode Git 如何混用SVN+GIT SVN 迁移到 Git 的简单原理 本文所涉及的工具只有一个 git-svn , 包含在1.7.1以上的git客户端版本内, 该工具详细介绍见: https://www.kernel.org/pub/softwar

PHP输出缓冲控制Output Control系列函数详解

  这篇文章主要介绍了PHP输出缓冲控制Output Control系列函数详解,本文讲解了输出缓冲的简介.输出缓冲的作用.php.ini 中的相关配置项.Output Control 函数详解等内容,需要的朋友可以参考下 概述 以前研究过PHP的输入输出缓冲,不过博客搬家以后,原来文章找不到了,今天看到一篇好文,顺便转载过来. 简介 说到输出缓冲,首先要说的是一个叫做缓冲器(buffer)的东西.举个简单的例子说明他的作用:我们在编辑一篇文档时,在我们没有保存之前,系统是不会向磁盘写入的,而是

Git 教程之标签详解_相关技巧

Git 标签 如果你达到一个重要的阶段,并希望永远记住那个特别的提交快照,你可以使用 git tag 给它打上标签. 比如说,我们想为我们的 w3cschoolcc 项目发布一个"1.0"版本. 我们可以用 git tag -a v1.0 命令给最新一次提交打上(HEAD)"v1.0"的标签. -a 选项意为"创建一个带注解的标签". 不用 -a 选项也可以执行的,但它不会记录这标签是啥时候打的,谁打的,也不会让你添加个标签的注解. 我推荐一直创

Git分支本地操作详解

引言 在上一节中我们对Git的常用本地操作的命令进行详解,而本节要讲解的是Git的分支, 在讲解之前补充两点概念性的东西: 第一个: 第一节中一个读者提出的疑问,Git和SVN在版本控制中存储方式版本信息的差异. 答:Git关心文件的整体是否发生变化,而SVN则关心的是文件内容的具体差异! SVN每次记录的是有哪些文件进行了修改,以及修改了哪些行的哪些内容: 如上图,比如版本2中记录的是文件A以及文件C的变化,而版本3中仅仅记录文件C 的变化这样,以此类推:而Git并不保存这些前后变化的差异数据

XSL系列函数详解

函数|详解 转载于:中国XML论坛 本期介绍多个XSL对于VBScript.JScript增加的方法.属性,以充分发挥XML的优势,用于< xsl:script >.< xsl:eval >标记内表达式的编写或< xsl:if >.< xsl:when >的expr属性. 一.absoluteChildNumber  含义:返回结点相对于它所有的兄弟(不论名字是否相同)的序号 语法:absoluteChildNumber(node) 参数:node ── 对

PHP输出缓冲控制Output Control系列函数详解_php实例

概述 以前研究过PHP的输入输出缓冲,不过博客搬家以后,原来文章找不到了,今天看到一篇好文,顺便转载过来. 简介 说到输出缓冲,首先要说的是一个叫做缓冲器(buffer)的东西.举个简单的例子说明他的作用:我们在编辑一篇文档时,在我们没有保存之前,系统是不会向磁盘写入的,而是写到buffer中,当buffer写满或者执行了保存操作,才会将数据写入磁盘.对于PHP来说,每一次像 echo 这样的输出操作,同样是先写入到了 php buffer 里,在脚本执行完毕或者执行了强制输出缓存操作,数据才会

Git 教程之基本操作详解_相关技巧

Git 基本操作 Git 的工作就是创建和保存你项目的快照及与之后的快照进行对比.本章将对有关创建与提交你的项目快照的命令作介绍. 获取与创建项目命令 git init 用 git init 在目录中创建新的 Git 仓库. 你可以在任何时候.任何目录中这么做,完全是本地化的. 在目录中执行 git init,就可以创建一个 Git 仓库了.比如我们创建 runoob 项目: $ mkdir runoob $ cd runoob/ $ git init Initialized empty Git

Git详解之四:服务器上的Git

原文链接:http://blog.jobbole.com/25944/ 原文:<Pro Git> 服务器上的 Git 到目前为止,你应该已经学会了使用 Git 来完成日常工作.然而,如果想与他人合作,还需要一个远程的 Git 仓库.尽管技术上可以从个人的仓库里推送和拉取修改内容,但我们不鼓励这样做,因为一不留心就很容易弄混其他人的进度.另外,你也一定希望合作者们即使在 自己不开机的时候也能从仓库获取数据 - 拥有一个更稳定的公共仓库十分有用.因此,更好的合作方式是建立一个大家都可以访问的共享仓