> 本文由云+社区发表
> 作者:工程师小熊
摘要:用了很久的Git和svn,由于总是眼高手低,没能静下心来写这些程序员日常开发最常用的知识点。现在准备开一个专题,专门来总结一下版本控制工具,让我们从git开始。完成本系列博客的阅读以后,你将掌握git的基本概念与git的基本命令,可以在本地随心所欲的完成代码的提交撤销保存修改等操作、可以流畅的参与多人协作,本文致力于快速的入门,如果涉及到更高级的功能需要进行更深一步的学习。
本文核心点:
- Git的基本概念
- 一个人使用Git时的代码版本控制--(提交、拉代码、分支操作)
- 多人合作时的代码版本控制--(合并冲突、暂存代码)
# 什么是Git
## 简介
git是世界上目前最先进的分布式版本控制系统,致力于团队、个人进行项目版本管理,完美的解决难以比较代码、难以合并代码、难以取消修改、难以在写当前代码的过程中保存未完成的修改去修改线上版本的bug等的痛点。
git是一个非常强大的工具,但作为一个git使用者来说,不用完全学习Git的知识点与命令,因为有的命令的使用频率非常的低甚至数年都不会用到,让我们来由浅入深进行学习。
## git的历史
git是linux的创始人linus,在付费版本控制工具BitMover收回对Linux社区免费使用权利的时候,一怒之下花费两个星期的时间写出来的。(牛笔的人)
# 开始
## 安装git
选择自己的操作系统对应的git版本安装,安装成功后运行`git version`后,输出git版本则安装正确。
git 官方: <https://git-scm.com/downloads>
## 配置用户信息
使用`git config`命令来配置用户名和邮箱
```js
git config --global user.name "pzqu"
git config --global user.email pzqu@example.com
```
> 如果用了 --global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 --global选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。
使用`git config user.name`和`git config user.email`来检查是否成功,也可以直接用`git config --list`来列出全部git配置信息来查看
![img](https://ask.qcloudimg.com/http-save/3097464/eut6rrnvjr.png?imageView2/2/w/1620)
## 创建git托管的项目
假如我们创建一个项目叫make_money,先创建一个文件夹叫make_money,再使用`git init`命令创建git项目。
```js
# pzqu @ pzqu-pc in ~/Documents/code/test [0:05:29]
$ mkdir make_money
# pzqu @ pzqu-pc in ~/Documents/code/test [0:06:24]
$ ls
make_money
# pzqu @ pzqu-pc in ~/Documents/code/test [0:06:29]
$ cd make_money
# pzqu @ pzqu-pc in ~/Documents/code/test/make_money [0:07:10]
$ git init
Initialized empty Git repository in /Users/pzqu/Documents/code/test/make_money/.git/
# pzqu @ pzqu-pc in ~/Documents/code/test/make_money on git:master o [0:07:12]
$ ls -al
total 0
drwxr-xr-x 3 pzqu staff 96 11 7 00:07 .
drwxr-xr-x 3 pzqu staff 96 11 7 00:06 ..
drwxr-xr-x 9 pzqu staff 288 11 7 00:07 .git
```
创建成功以后,会出现一个叫.git的隐藏文件夹,这个就是你的git仓库,以后所有的git操作历史提交记录信息就全部记录在此了,只要这个文件夹在就可以记住我们的全部git操作
## 工作区和暂存区
在使用git的时候还要清楚暂存区和工作区的含义,参考[廖雪峰的官方网站-git篇-工作区和暂存区](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/0013745374151782eb658c5a5ca454eaa451661275886c6000)
# 常见情况
## 提交代码
### 新文件与修改
```js
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [11:37:50]
$ ls
README.md
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [11:42:02]
$ touch file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [11:42:15]
$ git add file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [11:42:23]
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [11:56:38]
$ git commit -m "[+]add new file1.txt"
[master 66cc488] [+]add new file1.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file1.txt
```
上图操作包含:
- 创建新文件file1.txt
- add 添加修改的内容到索引
- status 查看修改的内容
- commit 把索引提交到本地分支
`git add .` :监控工作区的状态树,此命令会把工作时的所有变化提交到暂存区,包括文件内容修改(modified)以及新文件(new),但不包括被删除的文件。
`git add -u`:他仅监控已经被add的文件(即tracked file),他会将被修改的文件提交到暂存区。add -u 不会提交新文件(untracked file)。(git add --update的缩写)
`git add -A` :是上面两个功能的合集(git add --all的缩写)
![img](https://ask.qcloudimg.com/http-save/3097464/u3p66p5cb7.png?imageView2/2/w/1620)upload successful
```js
git show 列出最近一次的提交
```
> 对于commit:像这样,你不断对文件进行修改,然后不断提交修改到版本库里,就好比玩RPG游戏时,每通过一关就会自动把游戏状态存盘,如果某一关没过去,你还可以选择读取前一关的状态。有些时候,在打Boss之前,你会手动存盘,以便万一打Boss失败了,可以从最近的地方重新开始。Git也是一样,每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为commit。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个commit恢复,然后继续工作,而不是把几个月的工作成果全部丢失。
### 删除文件
```js
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [12:55:24]
$ ls
README.md file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [12:55:25]
$ git rm file1.txt
rm 'file1.txt'
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [12:55:30]
$ ls
README.md
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [12:55:32]
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [12:55:40] C:128
$ git commit -m "[-]delete file1.txt"
[master e278392] [-]delete file1.txt
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 file1.txt
```
上图操作包含:
- 创建新文件file1.txt
- git rm 删除file1.txt文件
- status 查看修改的内容
- commit 把索引提交到本地分支
tip1: 如果没有用git rm删除文件,在本地删除文件后,git add一下再提交可以达到同样的效果
tip2: 要是你加班太晚,头晕不小心删除了不想删除的文件怎么办?见
下一篇:版本控制工具——Git常用操作(下)-后悔药
## 拉代码
### 方法一 pull
```js
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [17:01:13]
$ git pull
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:pzqu/git_test
5fd4d8f..7b54a8a master -> origin/master
Merge made by the 'recursive' strategy.
share_file.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 share_file.txt
```
上图命令:
- git pull
查看本地仓库变化`git log`
![img](https://ask.qcloudimg.com/http-save/3097464/4dxbkpnji8.png?imageView2/2/w/1620)upload successful
上图可以看到向远程仓库pull的时候,出现了两个新的commit,`commit 7b54a8ae74...`的提交信息为`Create share_file.txt`,另一个`commit fdbb19cf4c51770`的提交信息为`Merge branch 'master' of github.com:pzqu/git_test`。事实上主线只有一个提交,为什么会出现这种情况? 是因为pull其实会做两个操作
- 拉远程仓库代码到本地
- 自动与当前分支合并并生成一个合并成功的提交
注意这里的第二个个步骤如果远程有人和你改了同一个文件就会出现一个冲突,这个时候git会提示你哪些文件有冲突,手动改了再提交一次就可以了。详情见合并冲突
### 方法二 fetch
我在远程修改了文件,向`share_file.txt`加了一行内容`tom modify`,此时拉代码。
```js
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [21:07:21]
$ git fetch
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [21:08:43]
$ git rebase origin/master
First, rewinding head to replay your work on top of it...
Applying: [+]add new file1.txt
Applying: [-]delete file1.txt
```
上图所示有以下两个操作
- fetch 拉取远端代码到本地
- rebase 把本地代码提交基于远端分支重新replay
效果如下:
![img](https://ask.qcloudimg.com/http-save/3097464/hdghbz2s9c.png?imageView2/2/w/1620)upload successful
上图是`git log`所输出的提交内容,刚刚pull的时候忘记把pull自动产生的merge提交到远程,rebase的时候把本地的提交放到了远程提交之后,看起来就是一条直线,比较优雅,也是推荐的方式。
同样的,如果产生了冲突,详情见合并冲突
## 分支操作
### 创建分支
分支是多人协同最经典的地方所在,我们来创建一个分支
```js
$ git checkout -b dev/pzqu origin/master
Branch 'dev/pzqu' set up to track remote branch 'master' from 'origin'.
Switched to a new branch 'dev/pzqu'
$ git branch
* dev/pzqu
master
```
- `git checkout -b 分支名 其他分支`,`-b`代表创建并切换到新建的分支,`分支名`代表新创建的分支叫什么名字,这里叫`dev/pzqu` ,`其他分支`代表基于哪一个分支来创建,这里基于远程的master分支`origin/master`,如果省略则代表基于当前分支
- `git branch`展示本地的分支情况,加`-a`参数可以展示全部的分支,包括远程分支
- `*`在分支前,指明了现在所在的分支是`dev/pzqu`
### 切换分支
```js
$ git checkout -b dev/pzqu2
Switched to a new branch 'dev/pzqu2'
$ git branch
dev/pzqu
* dev/pzqu2
master
$ git checkout dev/pzqu
Switched to branch 'dev/pzqu'
Your branch is up to date with 'origin/master'.
$ git branch
* dev/pzqu
dev/pzqu2
master
```
- 基于当前分支创建了一个新的分支并自动切换过去`dev/pzqu2`
- `git checkout 已存在的分支名`切换分支回到`dev/pzqu`
### 删除分支
```js
$ git branch
* dev/pzqu
dev/pzqu2
master
$ git branch -D dev/pzqu2
Deleted branch dev/pzqu2 (was 7c9be37).
$ git branch
* dev/pzqu
master
```
- 位于`dev/pzqu`,删除了`dev/pzqu2`分支
## 合并冲突
### 合并同一个分支的冲突(常见)
为了产生一个冲突,我在另一个地方向远程仓库提交了代码,更改`share_file.txt`文件,加了一行内容`tom add for merge`,
本地修改同一个文件加了一行`pzqu add for merge`,并提交到本地,这样一来,本地和远程仓库的同一个文件就不一样了,一会拉代码一定会产生一个冲突。效果如下:
![img](https://ask.qcloudimg.com/http-save/3097464/t7ydr9gb6e.png?imageView2/2/w/1620)upload successful
- 一般rebase或pull冲突的时候,都会出现提示,然后git status会出现上图图示
- 这个时候不可以进行任何分支切换和commit操作,按照他提示进行处理
- git status提示哪个文件是都被修改的,both modified,然后使用编辑器修改该文件,解决冲突
- 解决完成后,git add 添加该冲突文件
- git rebase --continue,并更新commit message,完成整个rebase流程 我们来看看这个冲突的文件:
![img](https://ask.qcloudimg.com/http-save/3097464/3rwxib9u0s.png?imageView2/2/w/1620)upload successful
Git用`<<<<<<<`,`=======`,`>>>>>>>`标记出不同分支的内容,我们修改如下后保存:
![img](https://ask.qcloudimg.com/http-save/3097464/uve6hug0dz.png?imageView2/2/w/1620)upload successful
`git add`再`git rebase --continue`后完成rebase,效果如下,再`push`的远程仓库即可
![img](https://ask.qcloudimg.com/http-save/3097464/2cr81n6tl8.png?imageView2/2/w/1620)upload successful
### 合并不同分支的代码产生冲突
关于怎么创建分支与切换分支见创建分支和切换分支,这里只讨论合并时产生的冲突的情况,我们已经基于`master`分支创建了一个`dev/pzqu`分支
```js
$ git branch
* dev/pzqu
master
```
切换到`master`分支,加一行`master add for merge`并提交,文件内容如下:
```js
$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
master add for merge
```
切换到`dev/pzqu`分支,向`share_file.txt`加入一行`dev/pzqu add for merge`并提交,现在`share_file.txt`内容如下:
```js
$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
dev/pzqu add for merge
```
现在两个分支的同一个文件内容不一样了,现在我们在`dev/pzqu`分支上进行合并:
```js
$ git merge master
Auto-merging share_file.txt
CONFLICT (content): Merge conflict in share_file.txt
Automatic merge failed; fix conflicts and then commit the result.
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:dev/pzqu x [11:17:31] C:1
$ git status
On branch dev/pzqu
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: share_file.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
<<<<<<< HEAD
dev/pzqu add for merge
=======
master add for merge
>>>>>>> master
```
上图出现了一个冲突,是我们意料之中的,修改`share_file.txt`文件,解决此冲突:
```js
$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
dev/pzqu add for merge
master add for merge
$ git add share_file.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:dev/pzqu x [11:22:40]
$ git commit -m "[*]merge master to dev/pzqu"
[dev/pzqu d9e018e] [*]merge master to dev/pzqu
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:dev/pzqu o [11:23:00]
$ git status
On branch dev/pzqu
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
```
冲突解决也提交了,看看我们现在的分支内容:
![img](https://ask.qcloudimg.com/http-save/3097464/a8m1ydt8t3.png?imageView2/2/w/1620)upload successful
上图我们可以看到:
- `master`分支比远程`origin/master`分支多一次提交,`dev/pzqu`分支由于是基于`origin/master`分支,合并了`master`分支的提交和当前`dev/pzqu`分支的提交,超出本地`master`两个提交,致此我们把`master`合并到`dev/pzqu`的操作就完成了。
- 通常我们开一个新的开发分支是为了在自己的分支上写代码,方便提交也不会把主线弄乱,现在我们用同样的方法将`dev/pzqu`合并到`master`分支,然后把两个分支都提交到远程。
```js
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
$ git merge dev/pzqu
Updating 58f047a..d9e018e
Fast-forward
share_file.txt | 1 +
1 file changed, 1 insertion(+)
$ git push origin master
Total 0 (delta 0), reused 0 (delta 0)
To github.com:pzqu/git_test.git
7c9be37..d9e018e master -> master
$ git push origin dev/pzqu
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 887 bytes | 887.00 KiB/s, done.
Total 9 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
remote:
remote: Create a pull request for 'dev/pzqu' on GitHub by visiting:
remote: https://github.com/pzqu/git_test/pull/new/dev/pzqu
remote:
To github.com:pzqu/git_test.git
* [new branch] dev/pzqu -> dev/pzqu
```
- 切换到`master`分支
- 合并`dev/pzqu`到`master`分支
- `master`推到远程仓库
- 如果`dev/pzqu`要保留,就可以推送到远程仓库。
![img](https://ask.qcloudimg.com/http-save/3097464/a0mebixdpe.png?imageView2/2/w/1620)upload successful
- 现在我们可以看到全部的分支都在一起了,强迫症都舒服了。
## 暂存代码保存现场
这种情况一般是出现在你正在完成一个功能,但是忽然线上发现了一个Bug,必须马上开一个新的分支来修复bug,但是现在的功能没写完不打算提交(commit),现在怎么办??不用怕暂存代码来帮助你。
```js
$ git status
On branch dev/pzqu
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: need_stash.txt
modified: share_file.txt
$ git stash
Saved working directory and index state WIP on dev/pzqu: d9e018e [*]merge master to dev/pzqu
$ git stash list
stash@{0}: WIP on dev/pzqu: d9e018e [*]merge master to dev/pzqu
$ git status
On branch dev/pzqu
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
//省略操作:去创建一个Bug分支,修复他并完成与主线的合并,删除Bug分支。
//省略操作:切回来当前分支继续开发
//下面来恢复现场
$ git stash apply stash@{0}
On branch dev/pzqu
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: need_stash.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: share_file.txt
```
- `status`查看到有2个文件修改没有提交
- `stash`把修改放到暂存区,并生成一个id
- `stash list`列出暂存区所有内容
- `stash apply`重新把暂存区内容放到本地
这里的`stash apply`成功的把暂存区的一次暂存恢复到了本地,但是暂存区还有会保存这次暂存,如果想删除这次暂存要用`git stash drop`来删除;也可以用`git stash pop`,恢复最后一次暂存的同时把stash内容也删了。
```js
$ git stash drop stash@{0}
Dropped stash@{0} (bfdc065df8adc44c8b69fa6826e75c5991e6cad0)
$ git stash list
```
好了,暂存区清干净了。
注意:要放到暂存区的文件一定要先通过git add加到index
# 小结
本文阅读结束以后,我们学会了
- Git的基本概念,知道git的作用、历史;学会安装配置Git,使用Git创建项目托管以及工作区和暂存区的概念
- 学会Git的本地操作,提交、拉代码、创建切换删除分支操作,
- 多人合作时的代码版本控制,学会了不同情况下的合并冲突、暂存代码操作
# 下集预告
Git常用操作(下)我计划给大家介绍以下点:
- 后悔药-各种后悔操作(撤消commit,回滚,回退远程仓库等)
- 哎呀,提交的时候漏了文件
- tag操作
- git忽略不想提交的文件
# 注意事项
理论上,git日常用到的命令是 diff show fetch rebase pull push checkout commit status 等,这些命令都不会导致代码丢失,假如害怕代码丢失,可以预先commit一次,再进行修改,但切记
> 不可使用自己不熟悉的命令 任何命令,不要加上-f的强制参数,否则可能导致代码丢失
建议多使用命令行,不要使用图形界面操作
# 引用
[git官网](https://git-scm.com/docs)
[廖雪峰的官方网站-git篇](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000)
[hexo博客部署到vps](https://ihaoming.top/archives/c9c37af.html#more)
**此文已由腾讯云+社区在各渠道发布**
**获取更多新鲜技术干货,可以关注我们[腾讯云技术社区-云加社区官方号及知乎机构号](https://www.zhihu.com/org/teng-xun-yun-ji-zhu-she-qu/activities)**
有疑问加站长微信联系(非本文作者)