美文网首页基本功Git
Git 工作原理,基本操作,创建与合并分支

Git 工作原理,基本操作,创建与合并分支

作者: 专职跑龙套 | 来源:发表于2018-08-22 12:42 被阅读15次

    前东家使用 Perforce 做版本控制,现东家使用 Git。

    Git 工作原理

    Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。
    这类系统(CVS,Subversion,Perforce,Bazaar 等等)每次记录有哪些文件作了更新,以及都更新了哪些行的什么内容:


    其他系统在每个版本中记录着各个文化的具体差异

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


    Git保存每次更新时的文件快照

    工作区 Working Directory VS 暂存区 Staging Directory

    Git 工作流

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

    • 工作目录 Working Directory,它持有实际文件
      例如,我们添加了一个文件 NewFile.txt,那它就是在工作目录 Working Directory 中:

      工作目录 Working Directory
    • 暂存区 Staging Directory 或者叫 Index Directory,缓存区域,临时保存文件的改动
      我们通过 git add 命令添加到暂存区 Staging Directory 或者叫 Index Directory:

      暂存区 Staging Directory 或者叫 Index Directory
    • 仓库 Head 区,指向最后一次提交的结果
      我们通过 git commit 命令实际提交改动到 Head 区:

      image.png

    通过下面这个图可以清晰的表示不同区的切换:
    引用自:http://marklodato.github.io/visual-git-guide/index-zh-cn.html

    不同区的切换

    文件的三种状态

    • 已修改 (modified):表示修改了某个文件,但还没有提交保存
    • 已暂存 (staged):表示把已修改的文件放在下次提交时要保存的清单中
    • 已提交 (committed):表示该文件已经被安全的保存在本地数据库中了

    版本库

    工作区中的隐藏目录 .git,就是 Git 的版本库。
    .git 目录结构如下:

    .git 目录结构

    其中:

    • HEAD 文件:指示目前被检出的分支

      HEAD 文件
    • index文件:保存暂存区信息

    • objects 目录:存储所有数据内容

      objects 目录
    • refs 目录:存储指向数据(分支)的提交对象的指针

      refs 目录

    什么是 HEAD

    HEAD 默认指向指向当前分支的最新提交。


    HEAD 默认指向指向当前分支的最新提交

    也可以通过 git show HEAD 命令看到当前 HEAD 的位置:

    通过 git show HEAD 命令看到当前 HEAD 的位置
    • HEAD 可以指向某个分支,如果指向某个分支,则永远指向分支上的最新提交
    • 当然 HEAD 也可以指向某个提交

    什么是 origin 和 master

    通过 Git 来修改代码,主要包括如下三步:

    • 从 Git 取数据 git clone
      • 涉及到 远程服务器下的某个仓库下的某个分支 remote server/remote repository/remote branch
    • 改动代码
      • 涉及到 本地某个仓库下的某个分支 local repository/local branch
    • 将改动传回 Git git push
      • 涉及到 远程服务器下的某个仓库下的某个分支 remote server/remote repository/remote branch

    涉及到两个 repository:

    • 远程仓库 Remote Repository,在远程服务器上
    • 本地仓库 Local Repository,在本地工作区上

    找到最近创建的一个 Github 项目:https://github.com/chenxiangcyr/spring-cloud-config-repo-demo
    现在我们通过如下命令 Clone 到本地:
    git clone git@github.com:chenxiangcyr/spring-cloud-config-repo-demo.git

    在 Clone 完成之后,Git 会自动为你将此远程仓库命名为 origin,并下载其中所有的数据,建立一个指向它的 master 分支的指针。
    因此 origin 是远程仓库的名字,master 是分支的名字。
    我们用 (远程仓库名)/(分支名) 这样的形式表示远程分支,所以 origin/master 指向的是一个远程分支 remote branch。我们实际上是从这个远程分支 remote branch 中 Clone 数据到本地,但你无法在本地更改远程分支 remote branch 中的数据。
    通过 git branch -r 可以查看远程分支:

    查看远程分支

    我们也可以通过 cat .git/config 看到 origin 的含义:

    [core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = true
    [remote "origin"]
        url = git@github.com:chenxiangcyr/spring-cloud-config-repo-demo.git
        fetch = +refs/heads/*:refs/remotes/origin/*
    [branch "master"]
        remote = origin
        merge = refs/heads/master
    

    同时,Git 会建立一个属于你自己的本地 master 分支,它指向的是你刚刚从远程分支 remote branch 传到你本地的副本。
    通过 git branch 可以查看本地分支:

    本地分支

    总结:

    • master 就是本地分支 local branch
    • origin/master 是远程分支 remote branch
    • remotes/origin/masterorigin/master 的指向是相同的
    • 我们可以通过 git diff origin/master master 来显示 本地分支 与 远程分支 的区别

    随着你不断的改动文件,git add, git commitmaster 的指向会自动移动。最终你还要通过 git push 命令来将修改推送到远程分支。
    git push origin master:master

    • 我们看到有两个 master,其中前面一个为 source,后面一个为 destination
    • 该命令可以理解为,在本地仓库中找到名字为 master 的分支,使用它去更新远程仓库下名字为 master 的分支,如果远程仓库下不存在名字是 master 的分支,那么新建一个。
    • git push origin master (省略了<destination>,等价于 git push origin master:master

    Git 基本操作

    引用自:http://marklodato.github.io/visual-git-guide/index-zh-cn.html


    上面的四条命令在工作目录、暂存目录(也叫做索引)和仓库之间复制文件。
    • git add files 把当前文件放入暂存区域。

    • git commit 给暂存区域生成快照并提交。

    • git reset files 用来撤销最后一次 git add files,你也可以用 git reset 撤销所有暂存区域文件。
      例如我们修改了文件 NewFile.txt(添加了内容 123),通过 git add NewFile.txt 放入暂存区域,随后通过 git reset NewFile.txt 来撤销,但是修改的内容没有被撤销(依然是 123):

      git reset files 用来撤销最后一次 git add files
    • git checkout files 把文件从暂存区域复制到工作目录,用来丢弃本地修改。
      例如在上面的基础上执行 git checkout NewFile.txt,这时候 NewFile.txt 所修改的内容被撤销了。

      git checkout files 把文件从暂存区域复制到工作目录,用来丢弃本地修改

    也可以跳过暂存区域直接从仓库取出文件或者直接提交代码:


    • git commit -a相当于运行 git add 把所有当前目录下的文件加入暂存区域再运行 git commit
    • git commit files 进行一次包含最后一次提交加上工作目录中文件快照的提交,并且文件被添加到暂存区域。
    • git checkout HEAD files 从仓库历史中回滚到复制最后一次提交到工作区。

    diff 查看两次提交之间的变动

    • git diff NexFile.txt 查看工作目录与暂存区的变动
    • git diff HEAD NexFile.txt 查看工作目录与仓库中 HEAD 的变动
    • git diff --cached NexFile.txt 查看暂存区与仓库中 HEAD 的变动
    • git diff <branch> NexFile.txt 查看工作目录与仓库中某个分支的变动
    • git diff <commit> <commit> 查看两次 commit 之间的变动
      diff 查看两次提交之间的变动

    checkout

    checkout 命令用于从历史提交(或者暂存区域)中拷贝文件到工作目录,也可用于切换分支。

    • 当给定某个文件名(或者打开 -p 选项,或者文件名和 -p选项同时打开)时,Git 会从指定的提交中拷贝文件到暂存区域和工作目录。比如,git checkout HEAD NewFile.txt 会将提交节点 HEAD~ (即当前提交节点的父节点)中的 NewFile.txt 复制到工作目录并且加到暂存区域中。
    • 当不指定文件名,而是给出一个(本地)分支时,那么 HEAD 标识会移动到那个分支(也就是说,我们“切换”到那个分支了),然后暂存区域和工作目录中的内容会和 HEAD 对应的提交节点一致。
    • 如果既没有指定文件名,也没有指定分支名,而是一个标签、远程分支、SHA-1值或者是像 master~3 类似的东西,就得到一个匿名分支,称作 detached HEAD(被分离的HEAD标识)。

    reset

    reset 命令把当前分支指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录。

    如果不给选项,那么当前分支指向到那个提交。如果用 --hard 选项,那么工作目录也更新,如果用 --soft选项,那么都不变。
    如果没有给出提交点的版本号,那么默认用 HEAD。这样,分支指向不变,但是索引会回滚到最后一次提交,如果用 --hard 选项,工作目录也同样。

    reset

    cherry pick

    cherry-pick 命令"复制"一个提交节点并在当前分支做一次完全一样的新提交。

    cherry pick

    rebase

    交互式变基 (rebase)。它可以用来编辑提交信息,或者将多个提交压缩成一个提交。
    git rebase -i HEAD~n

    Git 创建与合并分支

    Git 关于分支的操作主要包括:

    • 查看分支git branch
    • 创建分支git branch <name>
    • 切换分支git checkout <name>
    • 创建 + 切换分支git checkout -b <name>
    • 合并某分支到当前分支git merge <name>
    • 删除分支git branch -d <name>

    每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。在 Git 里,这个分支叫主分支,即 master 分支。HEAD 严格来说不是指向提交,而是指向 mastermaster 才是指向提交的,所以, HEAD 指向的就是当前分支。每次提交,master 分支都会向前移动一步,这样,随着你不断提交,master 分支的线也越来越长:

    一个具体的 Case

    我们目前在 master 分支上,下一阶段要开发一个新需求付款,因此需要在一个新的分支 payment 上开发,测试,最后合并到 master 分支上。

    创建新分支:

    git checkout -b payment
    

    以上操作相当于:

    git branch payment
    git checkout payment
    

    随后我们在 payment 分支在不断的向前推进:

    这时候,突然来了一个线上的 Issue,需求紧急修复,因此我们停下手动上的工作,需要在一个新的分支 bugfix 上开发,测试,最后合并到 master 分支上。

    创建新分支:

    git checkout -b bugfix
    

    随后我们在 bugfix 分支上进行修改:

    测试没问题后,合并到 master 分支上:

    git checkout master
    git merge bugfix
    

    在合并的时候,由于当前 master 分支所指向的提交是 bugfix 的直接上游,所以 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧,这就叫做 “快进(fast-forward)”。

    随后我们上线该紧急修复,然后可以使用命令来删除 bugfix 分支:

    git branch -d bugfix
    

    接下来,我们切换到之前的 payment 分支上继续工作:

    测试没问题后,合并到 master 分支上:

    git checkout master
    git merge payment
    

    这和你之前合并 bugfix 分支的时候看起来有一点不一样。在这种情况下,你的开发历史从一个更早的地方开始分叉开来。 因为 master 分支所在提交并不是 payment 分支所在提交的直接祖先,Git 不得不做一些额外的工作。

    出现这种情况的时候,Git 会使用两个分支的末端所指的快照(蓝色部分)以及这两个分支的工作祖先(黄色部分),做一个简单的三方合并。


    引用:
    Git 教程 创建与合并分支
    3.2 Git 分支 - 分支的新建与合并
    浅析 Git 思想和工作原理
    图解Git
    Git 的origin和master分析(转)

    相关文章

      网友评论

        本文标题:Git 工作原理,基本操作,创建与合并分支

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