这是我参与「第五届青训营 」笔记创作活动的第16天
1. Git是什么
Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency
Git是一个免费的开源分布式版本管理系统,旨在快速高效地处理从小型到超大型的所有项目
1.1 版本控制
一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统
为什么需要版本控制?
- 更好的关注变更,来接每个版本的改动是什么,方便对改动的代码进行检查,预防事故发生;
- 也能够随时切换到不同的版本回滚误删误改的问题代码
1.2 Git基本原理
- 每个库都存有完整的提交历史,可以直接在本地进行代码提交
- 每次提交记录的都是完整的文件快照,而不是记录增量
- 通过
push等操作来完成和远端代码的同步
优点
- 分布式开发,每个库都是完整的提交历史,支持本地提交,强调个体
- 分支管理功能强大,方便团队合作,多人协同开发
- 【校验和机制】保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失。(删除可通过新建分支,然后让删除内容的分支成为新的分支)
缺点
- 相对SVN更复杂,学习成本更高
- 对于大文件的支持不是特别友好(git-lfs工具可以弥补这个功能)
2. Git基本使用方式
2.1 Git Config
Git 有如下从高到低三个级别的配置,每个级别的配置可能重复,但是低级别的配置会覆盖高级别的配置。
--system--global--local
常见Git配置
-
用户名配置
git config --global user.name "anthony_4926" git config --global user.email "Anthony_4926@163.com" -
Git命令别名配置
git config --global alias.cin "commit --amend --no-edit"
2.2开始版本控制
首先,找一个合适的地方创建一个需要进行版本控制的文件夹:
# mkdir demo
# cd demo
然后,通过git init命令初始化demo这个文件夹,使之成为能够进行版本控制
# git init
创建完成之后会在当前目录下生成一个隐藏文件。
tree .git
.git
├── branches
├── config
├── description
├── HEAD
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── prepare-commit-msg.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
2.2.1 工作区和版本库
刚刚初始化的文件夹有一个隐藏文件夹.git,这个隐藏的文件夹就是版本库。我们能看到的就是工作区。
向版本库中添加文件的时候是分两步执行的:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区(stage)的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改,然后清空暂存区。需要注意的是,只有放在暂存区中的内容才能提交到分支中,保存下这个改动的版本。
2.2.2 撤销修改
现在再工作区中新建一个文件,touch readme.md,并写入一些内容
把readme.md添加到暂存区,使用 git status可以查看当前版本库的状态
你可以发现,Git会告诉你,git restore --staged <文件>...可以取消暂存,就是把添加到暂存区的文件从暂存区移除。
现在我不想移除,我接着对工作区的文件进行了修改。
现在经理给了你一个任务,新的修改都不要了,还是要上一个版本的。这时,可以通过git checkout -- readme.md命令,使版本库的内容覆盖工作区中的内容,即恢复到上一个add时的版本。
git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。
2.2.3 删除文件
现在我们为删除文件做一些准备, 新建一个文件,然后添加到暂存区,最后提交。
我们开始我们的删除实战。
通常我们会在文件夹内直接删除deleted_file.txt,这个时候Git是知道我们删除了文件的。通过git status命令就可以知道哪些文件被删除了。
现在我们可以做两个操作
- 确实要删除
deleted_file.txt,此时我们可以用git rm命令删除,并且git commit
- 删错了,使用
git checkout -- deleted_file.txt从版本库中覆盖到工作区
2. 3 分支管理
每次提交,都会生成一个提交节点,可以理解为我们的工作是基于祖先节点继续向下进行的。Git把每个提交节点穿成一条线,这个线就是分支。
现在我们只有一个main分支,*表示我们当前正在哪个节点上。
执行 git log,我们能看到提交记录。下图commit后边的一串黄色字符串,如“2e6e365a67787dc9a13aa305823271d92b95aadd”,是每次提交的HashID,对应到我们的蓝色的图上就是各个节点。
2.3.1 Git Branch & Git Checkout
接下来,我们将要创建一个到名为 newImage 的分支。
git branch newImage
newImage 分支是基于C2节点创建的,即newImage 分支的线将从C2节点开始。但是,现在我么只是创建了一个分支而已,并没有站在这个分支上。要想在newImage 分支上向下工作,我们需要使用git checkout newImage命令切换到该分支上。我们也可以不先使用git branch命令创建分支,而是直接使用git checkout -b newImage命令,同样可以实现我们创建分支并切换的目的。
git checkout newImage
现在我么看到,*是在newImage 分支上了。接下来我们可以在该分支上进行一些工作,然后提交我们的工作节点。
我们发现main仍然停留在C2节点上,也就是说newImage 分支上的操作,不会影响其他分支。接下来我们再提交一次,更深刻理解一下。
2.3.2 Git Merge
我们准备了两个分支,每个分支上各有一个独有的提交。现在我们假设,C2和C3对C1的修改没有公共部分(如果修改了公共的部分,在合并时需要决定采用哪个修改,也就是要解决冲突)。
我们要把 bugFix 合并到 main 里。我们知道,当前分支是不能对其他分支进行操作的,要想把bugFix 合并到 main里其实是改变了main。所以,我们应该切换到main,然后把bugFix拉过来。
$ git checkout main
$ git merge bugFix
在拉的过程中,为了保留C2这个节点不被更改,Git在把C2复制了一份,然后把C3对C1的修改合并过来,得到C4节点,main分支前进一步。
2.3.3 Git Rebase
第二种合并分支的方法是 git rebase。Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
Rebase 的优势就是可以创造更线性的提交历史,这听上去有些难以理解。如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。
咱们还是实际操作一下吧……
现在让bufFix以main为父节点。Git会把C3对C1的修改,融入到C2的副本C2’中,这样C2’就拥有了对C1的全部修改。
2.3.4 分离HEAD
我们首先看一下 “HEAD”。 HEAD 是一个对当前检出记录的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录,图中是*。
HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。
HEAD 通常情况下是指向分支名的(如 bugFix)。在你提交时,改变了 bugFix 的状态,这一变化通过 HEAD 变得可见。
我们现在想基于C3节点向下开发,不在基于C4了,就可以执行
git checkout c3
2.3.5 再说撤销修改
现在我们再某个分支上提交了很多次,然后对C3进行了一些修改,突然来个需求,说C2的修改不好,重新接着C1向下修改。于是我们就可以重新定位到C1上,接着C1向下工作。
$ git checkout c1
$ git commit
2.3.6 Git Cherry-pick
git cherry-pick是一个真的就像是他的名字一样,是把其他节点摘过来的。
现在的位置是C1,我们接下来要把C3、C4、C7摘到C1下。
git cherry-pick c3 c4 c7
2.3.7 可视化分支关系
我们可以使用下边这个命令在命令行查看蓝色图那样的分支历史
git log --graph --pretty=oneline --abbrev-commit
2.4 Git Remote
2.4.1 绑定远程仓库
现在的状态是,我们本地已经有一个受版本控制的文件夹了,一般称之为仓库。现在想在GitHub上创建一个Git仓库,并且让本地的仓库和远程的仓库同步。
首先,登录GitHub,在右上角新建一个仓库
创建完毕后,这是一个空的仓库,也没有与本地的仓库绑定。我们可以通过GitHub给的提示在本地执行该命令git remote add origin git@github.com:Anthony-4926/demo.git,使本地与远程绑定。
通过 git remote -v可以查看我们绑定的远程仓库。我们会发现,下边有一个fetch的远程仓库,和一个push的远程仓库。
fetch的远程仓库是指我们从哪里拉项目到本地,覆盖本地的仓库。push的远程仓库是指本地的版本仓库推到哪里,覆盖远程的仓库。
我们可以添加多个不同的fetch和push。
2.4.2 从远程仓库克隆
克隆远程的仓库必须知道它的地址,Github上有。
执行git clone git@github.com:Anthony-4926/demo.git,就可以克隆到本地了。克隆到本地的仓库自动和远程绑定。
2.4.3 推送分支
这里我们先大概知道一下推送分支使用git push命令,具体的我们在再讲