美文网首页
Git分布式版本控制

Git分布式版本控制

作者: 奚山遇白 | 来源:发表于2018-09-09 23:13 被阅读0次

    关于版本控制

    首先我们来了解一下什么是“版本控制”?所谓版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。例如如果你希望能对某个项目的的每一个修订版本进行记录,想要可以很轻松的回溯到某一次修改,想要比较文件的变化,想要查出是谁进行了某次修改等等功能,你都可以使用版本控制来实现。

    本地版本控制系统

    人们很久之前就开发了许多种本地版本控制系统,主要原理都是采用某种简单的数据库来记录文件的历次更新差异,从而通过文件和每次的版本差异记录,重新计算出各个版本的文件内容。原理如下图所示:


    本地版本控制

    集中化的版本控制系统

    但是本地版本控制只是针对个人项目,如果是对于不同系统的开发者进行协同工作的情况则不适用,所以集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)出现了。通过一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新,从而实现不同开发者协同开发。


    集中式版本控制

    分布式版本控制系统

    CVCS虽然解决了协同开发,但是因为是集中式的管理,所以如果中央服务器故障或损坏,将严重影响开发,所以分布式版本控制系统(Distributed Version Control System,简称 DVCS)应运而生


    分布式版本控制

    如上图所示,每个客户端并不只提取最新版本的文件快照,而是对代码仓库的完整备份。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 而我们要介绍的git也是一种分布式版本控制系统。

    Git 基础

    Git和其它版本控制系统的区别

    Git和其它版本控制系统的主要差别在于Git记录数据的方法。其它大部分系统以文件变更列表的方式存储信息,将每次更新提交保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异。


    按文件变更保存修改

    而git对于每次提交则相当于对整个系统的一组快照,文件有修改时记录版本差异,文件无修改时保留一个链接指向之前存储的文件。


    快照

    Git 保证完整性

    Git 中所有数据在存储前都使用 SHA-1 散列(hash,哈希)机制来计算校验和,然后以校验和来引用。 这意味着不可能在Git不知情时更改任何文件内容或目录内容。而且实际上,Git数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名.

    Git 一般只添加数据

    你执行的 Git 操作,几乎只往 Git 数据库中增加数据。 这保证了一旦数据被提交就难以再丢失数据,所以对于我们定期推送数据的项目来说,增加了安全性。但是凡事总有两面,使用git来管理的项目因为这一特性可能会越来越大,那么我们用来克隆项目的时间就会随之增加,就像我们管理某个.a/.framework包来说,虽然每次可能只会增加100M,但如果一直放在一个仓库中管理的话,由于仓库会越来越大,所以update时间会越来越长。所以我们换用了软链接的形式来管理,那么实际的framework并不归于项目直接引用的库管理,项目直接引用的库中我们只是用来存储.podspec文件,根据.podspec文件再去进行对应版本实际功能库的下载。那么直接引用的仓库变化不会太大,而且能够更清晰的看到依赖库版本的变更。

    Git的一些基本概念

    Git 有三种状态,你的文件可能处于其中之一:已修改(modified)/已暂存(staged)/已提交(committed)
    已修改表示修改了文件,但还没保存到数据库中。
    已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交
    已提交表示数据已经安全的保存在本地数据库中。
    而这三种状态也引入 Git 项目的三个工作区域的概念:工作目录/暂存区域/Git 仓库

    工作区域
    Git的基本工作流程如下:
    【1】在工作目录中修改文件。
    【2】暂存文件,将文件的快照放入暂存区域。
    【3】提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。

    Git的常用命令

    基础命令

    clone

    //从指定地址克隆项目到本地,最后一个参数为你想要的本地仓库名称,不填写该参数的话就按照远端项目名称默认命名
    $ git clone https://github.com/yourprojecturl localProjectName
    

    add

    //实现对指定文件的跟踪,即将工作目录中的文件添加到暂存区域进行跟踪,filename可以是具体的文件名称,也可以用*或者.指代工作目录下的所有文件
    $git add fileName
    

    commit

    //提交暂存区域的修改到Git 仓库,最后的参数是对这次提交的具体描述
    $ git commit -m 'commitInfo'
    

    reset

    //取消对于文件的暂存
    $ git reset HEAD fileName
    

    status

    //要查看哪些文件处于什么状态
    $ git status
    

    例如我们在刚刚执行了clone命令或者刚刚执行了提交本地无任何改动的情况下执行该命令,如下所示,你会看到结果提示我们工作目录很干净,无任何改动需要提交

    $ git status
    On branch master
    nothing to commit, working tree clean
    

    现在我们在项目中加入一张图片,再次执行status命令看一下结果,如下所示,显示了我们有一个文件未被跟踪,可以使用add命令添加跟踪

    $ git status
    On branch master
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    
        SQYAlertView/Assets/icon.png
    
    nothing added to commit but untracked files present (use "git add" to track)
    

    现在我们使用add命令将文件添加跟踪看一下结果,如下所示,显示了我们有一个新建的文件,我们可以执行提交命令,也可以使用git reset命令取消追踪

    $ git status
    On branch master
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
    
        new file:   SQYAlertView/Assets/icon.png
    

    现在我们使用reset命令取消追踪然后查看文件状态,如下所示,文件变成了未跟踪状态

    $ git status
    On branch master
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    
        SQYAlertView/Assets/icon.png
    
    nothing added to commit but untracked files present (use "git add" to track)    
    

    上述几个命令对于文件状态的影响如下图所示:


    文件状态变更

    相信通过上述几项命令的介绍,大家应该更加理解了git的基本工作流程中对于文件状态的记录过程。

    分支的新建与合并

    我们在实际的项目开发中总是会遇到好几个需求并行开发的情况,但是这些需求之间其实互不干扰,这个时候我们可以使用分支,在不同分支上进行不同需求的开发,待每个需求开发自测完成再合并到同一分支上即可,这样可以减少开发之间的合并冲突。

    checkout

    //新建一个分支并将当前工作区切换到该分支
    $ git checkout -b branchName
    //或者你可以使用下列命令切换到某一个已经存在的分支,例如master
    $ git checkout branchName
    

    需要注意的是:当你切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子保持一致。

    merge

    //首先切换到你想要进行合并的基础分支
    $ git checkout baseBranchName
    // 然后使用merge命令合并你要合并的分支到当前基础分支上
    $ git merge targetBranchName
    

    例如目前这个工程的分支显示如下


    普通合并前

    我们使用merge命令将hotfix分支合并到master分支上,那么你的工程目录会像以下这样变化,结果是合并成功,两个分支指向同一个节点:


    普通合并后
    但是对于上图中的分支,假如你要将iss53分支合并到master分支上,git的表现可能有些不同,因为master分支所在提交并不是 iss53 分支所在提交的直接祖先,iss53分支是从更早的分支分开进行相应开开发的。这种情况下,Git 会使用两个分支的末端所指的快照(C4 和 C5)以及这两个分支的工作祖先(C2),做一个简单的三方合并
    特殊合并前

    Git会将三方合并的结果做了成一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。


    特殊合并后

    branch

    //删除某个不在需要的分支
    $ git branch -d branchName
    

    cherry-pick

    //遴选,即从不同的分支中检出一个单独的commit , 并把它和你当前的分支合并
    $ git cherry-pick commitID
    

    推送和抓取/拉取

    当协同开发时,你肯定会用到本地数据和远端仓库交互,这样才能让项目在不同的开发人员中流转同步。

    fetch

    //抓取远端某一分支的变更,但仅仅是抓取改变,它并不会自动合并或修改你当前的工作
    $ git fetch remoteName
    

    pull

    //自动的抓取然后合并远程分支到当前分支
    $ git pull <远程主机名> <远程分支名>:<本地分支名>
    //例如:要取回origin主机的next分支,与本地的master分支合并,如下所示
    $ git pull origin next:master
    

    push

    //推送你本地的修改到远端服务器
    $ git push <远程主机名> <远程分支名>
    

    tag

    //在实际开发中你还可以选择设定标签来标记当前项目状态,-a选项描述标签名称,-m选项描述标签具体信息
    $ git tag -a 'tagName' -m 'tagInfo'
    //还可以使用-l选项来查看当前项目的所有标签
    $ git tag -l
    

    git分支整合的另外一种方法

    除了merge可以合并分支之外,我们还可以使用rebase命令对分支树进行修改,我们先来看下面的分支情况:


    当前分支

    experiment分支和master分支符合进行合并呢?你可能会直接想到merge命令,如果我们使用merge命令,分支树会像下图所示:


    合并分支
    这样由于我们之前说过的三方合并,我们的分支树会产生一个新的c5节点,从图上可以看出,这样我们的分支树会变的臃肿,那么如何得到清爽的目录树呢?答案是使用rebase变基,顾名思义,变基即是改变基础节点,从图上我们可以看出experiment分支和master分支的基础节点是c2,那么假如我们使用了rebase,将experiment分支的基础节点变成master分支所指向的c3呢?那是不是experiment分支上的c4改变就相当于发生在master分支所指向的c3之后了?这样是否就省去了merge命令下由于三方合并产生的c5节点了?答案是yes,结果如下图所示,我们得到了更清爽的分支树:
    变基分支

    tip

    不论是使用merge还是rebase,代码都有可能出现冲突,不要怕,按照项目指出的冲突文件挨个解决冲突,解决完成后继续执行合并即可。我们多人协作开发时肯定会遇到多人修改相同文件的情况,冲突是不可避免的,但是绝不是不可解决的,沉着冷静梳理逻辑即可。

    使用git时的常见问题及解决方法

    本地代码书写错误

    checkout

    //重置某个目录下的修改(未提交到暂存区域)
    $ git checkout filePath
    

    reset

    //取消暂存的文件(已提交到暂存区域)
    $ git reset HEAD fileName
    //取消某一次提交,参数是提交的id(已提交)
    $ git reset commitid
    

    本地书写代码错误且推送到了远端

    //取消某一次提交,参数是提交的id(已推送)
    $ git reset commitid    
    //将取消提交后的代码推送到远端,并通知所有已经拉取错误代码的人拉取新的修复代码
    $ git push
    
    //或者使用revert方法
    $ git revert commitid
    

    tip

    reset和revert的不同在于执行revert命令撤回了提交的内容同时会增加了一条commit记录,用来记录这次撤销操作。

    本地未提交,拉取最新纪录,本地代码丢失

    这需要注意,形成拉取前检查本地是否有未提交修改的开发习惯,以防自己本地的代码被冲掉。目前对于一些图形化工具比如sourceTree来说如果你执行拉取操作,本地有文件未提交时会主动提示

    提交了部分已解决的冲突

    提交代码的的时候需要注意,尽量先将目标代码合并到自己的开发分支上,解决完冲突之后再反向合并,这样能最大程度的避免提交别人已经解决了的一些问题,减少多次相同冲突的合并,提升开发效率。

    脑洞大开

    【1】回退commit和暂存,保留源码

    git reset --mixed
    

    【2】回退commit,保留暂存和源码

    git reset --soft
    

    【3】彻底回退到某个版本

    git reset --hard commitID
    

    【4】回退到远程某个版本,比如master版本

    git reset --hard master
    

    需要注意reset重置操作不同参数的含义和操作不同,需根据需要进行使用,特别是hard参数一定要慎用,因为是强制重置所以可能会导致修改丢失reset
    【5】删除远程分支

    git push origin --delete branchName
    

    【6】删除远程master分支
    因为master为default默认分支,所以受到保护,可以先把default分支切换为别的分支,然后执行第5个问题中的删除远程分支即可
    【7】清理远程已删除的本地分支

    git remote prune origin
    

    【8】关联本地分支到远程另一个分支

    git branch –set-upstream 本地新建分支名 origin/远程分支名
    

    例如:

    git branch –set-upstream dev origin/dev //把本地dev分支和远程dev分支相关联。
    

    注:本地新建分支, push到远程服务器上之后,使用git pull或者git pull 拉取或提交数据时会报错,必须使用命令:git pull origin dev(指定远程分支);如果想直接使用git pull或git push拉去提交数据就必须创建本地分支与远程分支的关联。建立关联
    【9】显示某个文件每行最后的修改信息(修改者/修改时间)

    git blame fileName
    

    【10】显示某个文件每个版本的提交信息(修改者/修改时间/修改内容)

    git whatchanged fileName
    

    【11】显示某个版本的修改详情

    git log //查看所有提交版本的信息
    git show commitID //查看某次版本提交的详细信息
    

    【12】有多个远程库(代码相同,比如github/gitee/gitlab)的时候,怎么在一份本地代码仓库里面获取和提交代码

    git remote //不带参数,列出已经存在的远程分支
    git remote -v | --verbose //列出详细信息,在每一个名字后面列出其远程url
    git remote add shortName url //添加一个新的远程仓库, shortName可以用来指定一个简短的名称
    

    【13】修改的代码还未想提交,但是需要切换到另一个分支,怎么办

    git stash/git stash save //暂存
    git stash apply //应用暂存
    git stash pop //应用暂存并且删除这个暂存    
    

    参考链接

    相关文章

      网友评论

          本文标题:Git分布式版本控制

          本文链接:https://www.haomeiwen.com/subject/eccjgftx.html