图文并茂,手把手带你git入门

1 分布式版本控制和集中式版本控制

大家或多或少都听过git是一个分布式版本控制系统。

但是很多同学并不理解何为分布式,究其原因是因为从未了解过集中式版本控制是什么,这就导致面试官一问诸如“为什么选择git而不选择SVN?”这类的问题时一脸懵逼。

image-20201004122418342

而从上图我们可以发现集中式和分布式的最大区别就在于

集中式——只有一个主服务器,所有的开发者只能和主服务器通信

分布式——开发者和服务器之间可以互相通信,类似于p2p架构

因此,我们很容易发现集中式的问题,那就是中央服务器出现了单点故障, 比如宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作。 如果中心数据库所在的磁盘发生损坏,又没有做恰当备份,毫无疑问你将丢失所有数据——包括项目的整个变更历史,只剩下人们在各自机器上保留的单独快照。

而分布式的优势就在于客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。

2 git结构简要介绍

1,工作区(working directory):我们实际操作的资料夹(档案)

2,暂存区(stage snapshot):将工作区档案的快照添加到暂存区域

3,git目录(.git directory):提交暂存区域的档案快照永久地储存在Git目录

图片发布

比如我们需要为一个庞大的项目开发一个小功能,开发工作是在工作区(Working Directory)完成的,之后再将代码add到暂存区(Stage Snapshot),之后再将暂存区的代码commit到git目录(.git directory)

2.1 暂存区的作用

主要有两个好处,一个是分批、分阶段递交,一个是进行快照,便于回退

2.1.1 分批递交,降低commit的颗粒度

比如,你修改了 a.py, b.py, c.py, d.py,其中 a.py 和 c.py 是一个功能相关修改,b.py,d.py属于另外一个功能相关修改。那么你就可以采用:

git stage a.py c.py
git commit -m "function 1"
git stage b.py d.py
git commit -m "function 2"

2.1.2 分阶段递交

比如,你修改了文件 hello.py,修改了一些以后,做了git add heello.py动作,相当于对当前的hello.py 做了一个快照, 然后又做了一些修改,这时候,如果直接采用 git commit 递交,则只会对第一次的快照进行递交,当前内容还保存在 working 工作区

当前的最新修改,则需要再做一次 git add ,才能递交

3 四大场景玩转git

请注意:以下操作皆是通过命令行完成

3.1 准备工作——安装git并查看帮助文档

我们访问git官方网站的下载页面,即https://git-scm.com/downloads,上面有各大主流平台的安装说明

因为笔者使用的是macOS下面就以mac平台为例进行安装

  1. 安装homebrew
 $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" 
  1. 安装git
$ brew install git
  1. 测试git是否安装成功,若出现版本信息,即说明安装成功
$ git --version
git version 2.24.3 (Apple Git-128)
  1. 查看git帮助文档
$ git --help

3.2 场景一:Git 本地版本库的基本用法

3.2.1 创建本地版本库

  1. 登录github.com,注册账号,然后按照下图一步步地创建一个项目

image-20201005222055999

image-20201005222517092

image-20201005222622164

  1. 生成ssh公钥并进行git clone操作

对ssh公钥的说明:

你可以想象成github的仓库有一道【指纹+密码】锁,平时如果你想从仓库里拿点东西,或者放点东西进去,一般都要输入密码才行。而ssh公钥类似于你自己电脑的指纹,我们需要将这个指纹交给github,这样下次再想对仓库进行操作,就无须再输入登录密码了

详细说明请看这个链接

image-20201005224214811

之后打开命令行,输入如下操作

# 步骤一:生成ssh公钥(如果自己已经创建过公钥,直接跳到步骤二)
$ ssh-keygen -t rsa -C “您的邮箱地址” # 该命令输入之后,一路回车到结束即可

# 步骤二:找到ssh公钥位置
$ cd ~/.ssh
$ ls # 下面的 id_dsa.pub 文件就是公钥
authorized_keys2  id_dsa       known_hosts
config            id_dsa.pub

# 步骤三:查看ssh公钥并复制到github
$ cat ~/.ssh/id_rsa.pub

image-20201005230908689

再回到你创建的仓库主页面,复制ssh地址

image-20201005231015802

打开命令行,进行clone操作

$ git clone git@github.com:bluesbonewong/repoTest.git

Cloning into 'repoTest'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

# 大功告成,本地版本库已经被你建立成功啦

3.2.2 本地版本库的基本用法

3.2.2.1 暂存更改的文件

以项目自带的md文件为例,现在我将添加新的内容,然后打开命令行

$ git status # 查看当前工作区(workspace)的状态

image-20201006101500184

以下两行命令是将特定文件(FILES)或者当前目录下所有文件添加到暂存区(Index)

$ git add FILES # 指定文件或文件列表
$ git add .     # 用“.”表示当前目录(这条命令最为常用)

我们输入git add .后,再用git status查看状态

image-20201006101837075

当然,添加到暂存区的文件都是可以撤回的,输入如下命令即可

$ git reset HEAD FILES # 指定文件或文件列表
$ git reset HEAD # 全部撤回

如下两行命令的效果是放弃工作区的特定文件(FILES)或者所有文件的修改,注意会删除掉已修改未暂存的内容,不希望被覆盖的文件可以先使用git add将其添加到暂存区

$ git checkout -- FILES # 不要忘记“--” ,不写就把FILES当分支名了
$ git checkout . # 放弃所有文件的修改

3.2.2.2 把暂存区里的文件提交到本地仓库

首先我们使用git commit进行提交

$ git commit -m "提交备注" # -m意思是进行mark,之后才能在""内填写提交备注

之后,使用git log可以查看我们的提交记录(输入q可以退出查看)

$ git log

image-20201006111206546

当然提交到本地仓库的记录也是可以撤销(回退)的,需要使用git reset --hard命令

git reset --hard HEAD^ # HEAD^表示HEAD的前一个版本
git reset --hard HEAD^^ # HEAD^^表示HEAD的前两个版本
git reset --hard HEAD~n # HEAD~100表示HEAD的前n个版本
git reset --hard 128个字符的commit-id # 回退到commit-id所在的版本
git reset --hard 简写为commit-id的前7个字符

注意:HEAD是一个指针,指向的是你当前所在的版本位置,而不是最新的版本位置,也就是说HEAD会随着git reset操作变化,具体请看下图

IMG_93F28927C181-1

紧接着,如果你对git reset --hard命令反悔了,怎么办呢?这时候,如果你用git log查看版本记录,会发现,你看不到最新版本位置的记录了,因为git log只能查看HEAD及其之前(时间更早)的提交记录,这就会产生一个问题,我们可以通过git reset --hard回到过去,那怎么回到未来?那就要有办法查到HEAD指向的版本之后(时间更晚)的提交记录。

这时候我们就需要git refloggit reset ---hard commit-id命令了

$ git reflog # 查看所有的版本记录,自己记好所需要的commit-id前7个字符
$ git reset --hard 简写为commit-id的前7个字符

3.3 场景二:Git 远程版本库的基本用法

3.3.1 origin是什么

当我们使用git status之后,大家应该可以经常发现一个单词origin

image-20201006161339079

其实origin 是当你运行 git clone 时默认的远程仓库名字,只是提示你当前所在的本地版本库是有一个线上版本库存在的。而且这个远程仓库名字是可以被修改的,如果你运行 git clone -o booyah,那么你默认的远程分支名字将会是 booyah/master

不过和main被认为是主分支一样,现在大家都默认origin就是远程仓库名字,已经约定俗成了。

3.3.2 git pull和git push

首先,我们明确一件事——对于git而言,近乎所有操作都是本地执行

那么远程版本库的最大作用,其实就只有两个

$ git pull # 把最新的远程仓库的代码的下载到本地,并和本地仓库进行合并
$ git push # 把自己本地的仓库的代码上传到远程仓库

但值得注意的是,git pull这条指令其实包含了两个操作

第一个是git fetch,即获取远程仓库的最新代码

第二个是git merge,即对代码进行合并

3.3.3 merge的原理

git的merge原理简单来说就是使用line diff技术,即按行比对,将差异的部分作为一个增量补丁

如下图所示,项目在A版本处开始分叉,形成了两个分支,分别提交了B、D、F和C、E、G几个commit,这时希望将两个分支合并,只要将F与A的差异部分,以及G和A的差异部分,都放入工作区,如果有冲突解决冲突后就可以提交一个版本H,即完成了两个分支的合并

640

3.4 场景三:团队项目中的分叉合并

3.4.1 团队项目的流程介绍

  1. 克隆或同步最新的代码到本地存储库;
  2. 为自己的工作创建一个分支,该分支应该只负责单一功能模块或代码模块的版本控制;
  3. 在该分支上完成某单一功能模块或代码模块的开发工作;
  4. 最后,将该分支合并到主分支

特别注意的是默认的合并方式为"快进式合并"(fast-farward merge),会将分支里commit合并到主分支里,合并成一条时间线,与我们期望的呈现为一段独立的分支线段不符,因此合并时需要使用--no-ff参数关闭"快进式合并"(fast-farward merge)

3.4.2 分支的基本用法

$ git checkout -b myBranch # 将当前分支分叉出一个分支(如上命令分支名称为mybranch),并签出(checkout)到工作区
$ git branch # 查看所有分支,带"*"号的一栏表明当前所在分支
$ git checkout myBranch # 切换分支,切换到myBranch分支

假如要将mybranch合并到master,那么首先确保当期工作区处于main分支,之后再使用如下指令,将其它分支合并到主分支。

当然,别忘了加上--no-ff

$ git merge --no-ff mybranch # 将myBranch合并到main分支

3.5 场景四:Git Rebase【变基】

首先,我们理解一下rebase这个单词,base是根基的意思,re-这个前缀有重新再来的意思,合并起来就是重新变换一下根基,即变基

3.5.1 为何要变基

在场景三我们提到过,为了保证分支的时间线也能独立,因此我们需要通过--no-ff来关闭快进式合并。

但这也带来另一个问题,那就是提交历史会变得混乱,例如向开源项目做贡献时。

请记住:变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样,但实际上不同的提交

3.5.2 变基的基本操作

如下图,整合分支最容易的方法是 merge 命令。

它会把两个分支的最新快照(C3C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)

通过合并操作来整合分叉了的历史。

其实,还有一种方法:你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。 在 Git 中,这种操作就叫做 变基(rebase)。 你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样

在这个例子中,你可以检出 experiment 分支,然后将它变基到 master 分支上:

$ git checkout experiment # 切换到experiment分支
$ git rebase master # 执行变基操作

它的原理是首先找到这两个分支( experimentmaster) 的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件(即C4'), 然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用

将  中的修改变基到  上。

现在回到 master 分支,进行一次快进合并

$ git checkout master
$ git merge experiment

 分支的快进合并。

在这个例子中,你会发现mergerebase这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的, 但它们看上去就像是串行的一样,提交历史是一条直线没有分叉

参考

  1. https://mp.weixin.qq.com/s/Km5KuXPETvG0wCGHrvj9Vg
  2. https://git-scm.com/book/en/v2
posted on 2020-10-07 17:31  大汪_0  阅读(290)  评论(0)    收藏  举报