git分享

220 阅读13分钟
在日常工作中,必不可少的需要运用git进行版本管理。本文主要介绍git的基本原理,以及相应git命令的使用场景

git是什么?

Git 是目前世界上最优秀的分布式版本控制系统。

版本控制系统有哪些?

1、版本控制(Revision control)简单说就是用于管理多人协同开发项目的技术。

2、版本控制系统常见类别有两种:

  • 集中式版本控制系统,如SVN:

       所有的版本数据都保存在服务器上,协同开发者从服务器上同步更新或上传自己的修改。用户的本地只有自己以前所同步的版本,不连网时,用户看不到历史版本,也无法切换版本验证问题,或在不同分支工作。同时,所有数据保存在单一服务器上,若服务器损坏,会丢失所有的数据。

  • 分布式版本控制系统,如GIT

       分布式版本控制系统是分布式的,当你从中央服务器拷贝下来代码时,你拷贝的是一个完整的版本库,包括历史纪录,提交记录等,这样即使某一台机器宕机也能找到文件的完整备份。

怎么用git?

OK,咱们进入正题,工作中如何使用git。不少同学表示git命令多且杂,不便于记忆,使用时容易出错与乱套。这主要是因为我们不熟悉其基本运行原理,以及英文太差导致(哈哈哈)。故准备了以下内容,git工作流程梳理;git专有名词、常用命令释义。

一、git三板斧

我们工作开发中经常用到的三个命令,git pullgit commitgit push;拉取,提交,推送。

是的,对于这三个命令我们同下图小哥一样熟练,熟练得能给你来段rap。


等等,貌似我们提交时还有git add,拉取时还有git fetch,这又和上述三个命令之间有什么关系呢。好,我们接下来从git流程入手逐步展开。

二、git流程梳理



我们先介绍四个专有名词

  • Workspace:工作区
  • Index / Stage:暂存区
  • Repository:仓库区(或本地仓库)
  • Remote:远程仓库

工作区

Workspace / Working Directory 就是平时存放项目代码的地方。

暂存区

Index / Stage 文件修改后,还未进入本地仓库时所暂时存放的地方。即,git add 后文件存放处 。

不保存文件实体, 通过id指向每个文件实体。可以使用 git status 查看暂存区的状态。暂存区标记了你当前工作区中,哪些内容是被git管理的。

注:存放在 ".git目录下" 下的index文件(.git/index)中

本地仓库

Repository 保存了对象被提交过的各个版本。 git commit后同步index的目录树到本地仓库。

注:存放在工作区中“.git”目录下。

远程仓库

Remote 远程仓库的内容可能被分布在多个地点的处于协作关系的本地仓库修改,因此它可能与本地仓库同步,也可能不同步。


简单了解这几个名词后,我们再回顾这张图。

1、从左至右做一次简单的流程介绍:

  1. 小明敲了半天代码,觉得差不多了,将改动点添加到暂存区 Index / Stage  ------ git add
  2. 将改动点提交至本地仓库 Repository ------ git commit
  3. 将本地仓库内容推送远端 Remote ----- git push

2、从右至左也来一遍:

  1. 小明写得太烂被离职,今天大明接手工作
  2. 大明从远端克隆一份到本地仓库 ----- git clone
  3. 大明划水半天后,决定开始工作;将远端最新内容拉到本地仓库中(因为玩了半天,可能别人有更新) ----- git fetch
  4. 看了下本地仓库代码除了if else 多了点没啥大问题,决定检出到自己的分支 ----- git checkout

git pull呢,貌似没有用到,好像和 git fetch 不太一样?

git fetch 是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中。

git pull  则是将远程主机的最新内容拉下来后直接合并,即: git pull = git fetch + git merge(合并),这样可能会产生冲突(conflits ,需要手动解决。


三、git基本原理

现在已经明白Git的基本流程,但Git是怎么完成的呢?Git怎么区分文件是否发生变化?下面简单介绍一下Git的基本原理。

1、首先我们需要了解的是,Git是一个分布式版本控制系统,保存的是文件的完整快照,而不是差异变化或者文件补丁。

2、Git每一次提交都是对项目文件的一个完整拷贝。为了高效,如果文件没有修改,Git不再重新存储该文件,而是只留一个链接指向之前存储的文件。

3、Git采用HashTable的方式进行查找,通过简单的存储键值对的方式来实现内容寻址,key是文件头和内容组成的40位hash值,value是压缩过后的文件内容。

上述内容不太好理解,我们通过查看git的目录结构以及分析一次Git提交来逐步理解

.git目录

.git目录是Git的核心,每一个变动都会存储在.git文件夹中

.git目录下有几个重要的文件/文件夹

  • config文件,主要存储项目的一些配置信息
  • objects文件夹, 存储git对象
  • HEAD文件,记录当前的头指针
  • index文件,存储暂存区的信息
  • refs文件夹, 存储分支的指针

git对象

git object 是git储存信息的最小单元

git object 存储在.git目录下的objects文件夹中,Git会将git对象压缩成二进制文件,git对象文件名为sha-1算法得到的hash值,按照2/38的形式保存,前两位是文件夹的名称,剩下38位是文件名。

git object 主要有 blob、tree、commit

举例:

  • git初始化,新增两个文件a.txt b.txt,并加入暂存

    $ git init 
    $ echo '111' > a.txt 
    $ echo '222' > b.txt 
    $ git add *.txt
  • 查看.git 目录

    $ tree .git/objects
    .git/objects
    ├── 58
    │   └── c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
    ├── c2
    │   └── 00906efd24ec5e783bee7f23b5d7c941b0c12c
    ├── info
    └── pack

  • 查看不同文件 git cat-file -t 查看文件类型 git cat-file -p 查看文件具体内容

    $ git cat-file -t 58c9
    blob
    $ git cat-file -p 58c9
    111

  • 执行git commit 多出两个文件,同样方法检测其类型和内容。

    $ git commit -m '[+] init'
    $ tree .git/objects
    .git/objects
    ├── 0c
    │   └── 96bfc59d0f02317d002ebbf8318f46c7e47ab2
    ├── 4c
    │   └── aaa1a9ae0b274fba9e3675f9ef071616e5b209
    
    
    文件一
    $ git cat-file -t 4caaa1 // 类型为 tree 
    tree
    $ git cat-file -p 4caaa1 // 内容为 一个目录结构
    100644 blob 58c9bdf9d017fcd178dc8c0...	a.txt  // 文件权限,文件类型,文件内容,文件名
    100644 blob c200906efd24ec5e783bee7...	b.txt
    
    文件二
    $ git cat-file -t 0c96bf // 类型为 commit
    commit
    $ git cat-file -p 0c96bf // 内容为 本次提交的快照,作者,内容,时间等
    tree 4caaa1a9ae0b274fba9e3675f9ef071616e5b209
    author xxx 17711112222 +0800
    committer xxx 17711112222 +0800[+] init

由上述流程可知三种对象的组织关系为:


小结:

至此我们可以可以再结合git的工作流程,来看下git内部是怎么运作的。继续对上述的例子进行操作。

  • 此时本地仓库中有两个文件 a.txt b.txt  对a.txt 进行修改。
  • git add a.txt 放入暂存。git在本地版本库中新增一个bolb object 假定为 A。
  • git commit 提交暂存中的信息。
  1. 保存目录结构(生成tree对象):git在本地版本库中新增一个tree object  假定为 newT。该tree object 中的目录结构由 A 与之前未曾变更的 b.txt对应的blob object 构成。
  2. 提交目录结构 git在本地版本库中新增一个commit object 。该commit object 中包含新生成的tree object  及newT
  3. 更新分支(更新分支指向的hash)将指针head master 指向新的commit

***此外 .git目录下还包含一个重要文件夹refs,主要用于分支的管理。本地分支,远程分支,分支暂时备份等。

refs包含文件夹:

  • heads:目录下有关于本地仓库的所有分支,使用git branch查看本地分支时,查询出的就是取的heads文件夹下所有文件的名称。
  • stash:存储的是所有你使用git stash命令暂存的记录,是一个列表。使用git stash list时,查询出的列表就是从stash中取出来的。
  • remotes:目录下有关于远程仓库的所有分支。使用git branch -a 查询出的远端分支就是remotes文件夹下所有文件的名称。
  • tags:对某一时间的版本打上标签

四、Git Flow

Git Flow 的常用分支

  • Production 分支

  1. 也就是我们经常使用的Master分支,这个分支最近发布到生产环境的代码,最近发布的Release, 这个分支只能从其他分支合并,不能在这个分支直接修改
  • Develop 分支

  1. 这个分支是我们是我们的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并与其他分支,比如Feature分支
  • Feature 分支

  1. 这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release
  • Release分支

  1. 当你需要一个发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到Master和Develop分支
  • Hotfix分支

  1. 当我们在Production发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release



五、git常用命令

***HEAD

首先了解下HEAD。HEAD,它始终指向当前所处分支的最新的提交点。所处的分支变化了,或者产生了新的提交点,HEAD就会跟着改变。

基本用法


  • git add files
    把当前文件放入暂存区域。
  • git commit 给暂存区域生成快照并提交。
  • git reset --files 用来撤销最后一次git add files,你也可以用git reset 撤销所有暂存区域文件。
  • git checkout --files把文件从暂存区域复制到工作目录,用来丢弃本地修改。

add 添加

将工作区修改的内容提交到暂存区,交由git管理。

  • git add . 添加当前目录的所有文件到暂存区
  • git add [dir] 添加指定目录到暂存区,包括子目录
  • git add [file1] 添加指定文件到暂存区

commit 提交

将暂存区的内容提交到本地仓库,并使得当前分支的HEAD向后移动一个提交点。

  • git commit -m [message] 提交暂存区到本地仓库,message代表说明信息 
  • git commit [file1] -m [message] 提交暂存区的指定文件到本地仓库 
  • git commit --amend -m [message] 使用一次新的commit,替代上一次提交
  • git commit -a 把所有当前目录下的文件加入暂存区域再运行git commit

status 状态

git status 查看文件此时状态;四种状态


  • Untracked: 未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过git add 状态变为Staged.

  • Unmodify: 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为Modified. 如果使用git rm移出版本库, 则成为Untracked文件

  • Modified: 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过git add可进入暂存staged状态, 使用git checkout 则丢弃修改过, 返回到unmodify状态, 这个git checkout即从库中取出文件, 覆盖当前修改

  • Staged: 暂存状态. 执行git commit则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify状态. 执行git reset HEAD filename取消暂存, 文件状态为Modified

checkout 检出

检出命令git checkout是git最常用的命令之一,同时也是一个很危险的命令,因为这条命令会重写工作区

  • git checkout branch 检出branch分支。
  • git checkout -- filename 用暂存区中filename文件来覆盖工作区中的filename文件。相当于取消自上次执行git add filename以来(如果执行过)的本地修改。
  • git checkout branch -- filename  维持HEAD的指向不变。用branch所指向的提交中filename替换暂存区和工作区中相 应的文件。注意会将暂存区和工作区中的filename文件直接覆盖。 

branch 分支

主要有展示分支,切换分支,创建分支,删除分支这四种操作。

  • git branch 列出所有本地分支 
  • git branch -r 列出所有远程分支 
  • git branch -a 列出所有本地分支和远程分支 
  • git branch [branch-name] 新建一个分支,但依然停留在当前分支 
  • git checkout -b [branch-name] 新建一个分支,并切换到该分支 
  • git branch --track [branch][remote-branch] 新建一个分支,与指定的远程分支建立追踪关系 
  • git checkout [branch-name] 切换到指定分支,并更新工作区 
  • git branch -d [branch-name] 删除分支 
  • git push origin --delete [branch-name] 删除远程分支 

merge 合并

将不同的分支合并起来

一般在merge之后,会出现conflict,需要针对冲突情况,手动解除冲突。

  • git fetch [remote] merge之前先拉一下远程仓库最新代码 
  • git merge [branch] 合并指定分支到当前分支

push 推送

上传本地仓库分支到远程仓库分支,实现同步。

  • git push [remote][branch] 上传本地指定分支到远程仓库 
  • git push [remote] --force 强行推送当前分支到远程仓库,即使有冲突 
  • git push [remote] --all 推送所有分支到远程仓库

reset 回退 

  • git reset 修改HEAD的位置,即将HEAD指向的位置改变为之前存在的某个版本

适用场景: 如果想恢复到之前某个提交的版本,且那个版本之后提交的版本我们都不要了,就可以用这种方法。

  • git reset —soft [commit] 只改变提交点,暂存区和工作目录的内容都不改变 
  • git reset —mixed [commit] 改变提交点,同时改变暂存区的内容 
  • git reset —hard [commit] 暂存区、工作区的内容都会被修改到与提交点完全一致的状态 
  • git reset --hard HEAD 让工作区回到上次提交时的状态


revert 反做

  • git revert   用一个新提交来消除一个历史提交所做的任何修改。

适用场景:如果我们想撤销之前的某一版本,但是又想保留该目标版本后面的版本,记录下这整个版本变动流程,就可以用这种方法。


diff 差异

  • git diff 显示暂存区和工作区的差异 
  • git diff HEAD 显示工作区与当前分支最新commit之间的差异 

六、HEAD 与 branch

HEAD:

  • 当前commit的引用

branch:

  • HEAD 是 Git 中一个独特的引用,它是唯一的。而除了 HEAD 之外,Git 还有一种引用,叫做 branch(分支)。HEAD 除了可以指向 commit,还可以指向一个branch,当指向一个branch时,HEAD会通过branch间接指向当前commit,HEAD移动会带着branch一起移动。
  1. HEAD 指向的 branch 不能删除。如果要删除 HEAD 指向的 branch,需要先用 checkout 把 HEAD 指向其他地方。
  2. 由于 Git 中的 branch 只是一个引用,所以删除 branch 的操作也只会删掉这个引用,并不会删除任何的 commit。