Git是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。分布式相比于集中式的最大区别在于开发者可以提交到本地,每个开发者通过克隆,在本地机器上拷贝一个完整的Git仓库。
一、版本管理
1.1 创建版本库
版本库(repository)可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,Git能对每个文件的修改、删除进行跟踪,以便在将来某个时刻可以进行还原。
通过git init
命令将目录变成Git仓库
$ git init
Initialized empty Git repository in C:/Users/yyz/Desktop/test/.git/
此时一个普通目录就变成了Git仓库,可以发现目录下多了一个.git
文件夹,这个目录是Git来跟踪管理版本库的。
1.2 添加文件到版本库
现在向test
目录或其子目录下创建一个 readme.txt 文件,内容如下:
Git is a version control system.
Git is free software.
第一步,用命令git add
把文件添加到Git仓库:
$ git add readme.txt
第二步,用命令git commit
把文件提交到Git仓库(-m
后面输入的是本次提交的说明)
$ git commit -m "wrote a readme file"
[master (root-commit) eaadf4e] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
执行
git commit
命令后会告诉你,1 file changed
:1个文件被改动(新添加的readme.txt文件);2 insertions
:插入了两行内容(readme.txt有两行内容)。
为什么Git添加文件需要add
,commit
一共两步呢?因为commit
可以一次提交很多文件,所以你可以多次add
不同的文件,比如:
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
1.3 版本管理
现在已经成功添加并提交了一个 readme.txt 文件,接着继续修改 readme.txt,内容如下:
Git is a distributed version control system.
Git is free software.
运行git status
命令查看状态:
$ git status
On branch master
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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
git status
命令可以查看仓库当前的状态,通过输出得知 readme.txt 被修改过了,但还没有准备提交的修改。
假如你休假两周之后继续上班,但已经记不清上次怎么修改的 readme.txt,这时可以用git diff
这个命令看看:
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
git diff
即查看difference,显示的格式是Unix通用的diff格式,通过输出看到,我们在第一行添加了一个distributed
单词。现在就知道了上一次对 readme.txt 做了什么修改,现在对文件进行提交。
$ git add readme.txt
执行git commit
之前,再查看一下当前仓库的状态
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
git status
告诉我们,将要被提交的修改包括 readme.txt,下一步提交
$ git commit -m "add distributed"
[master e475afc] add distributed
1 file changed, 1 insertion(+), 1 deletion(-)
提交后,再用git status
命令查看仓库的当前状态:
$ git status
On branch master
nothing to commit, working tree clean
可以看到当前没有需要提交的修改,工作目录是干净的。
1.4 版本回退
可以把Git中的
commit
理解成“快照”,每当觉得文件修改到一定程度的时候,就可以保存一个快照,当你把文件改乱或者误删,还可以从最近的一个commit
恢复。
在实际工作中,我们怎么知道 readme.txt 文件一共有几个版本被提交到Git仓库里了呢?这时就用到git log
命令了。
$ git log
commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master)
Author: Michael Liao <askxuefeng@gmail.com>
Date: Fri May 18 21:06:15 2018 +0800
append GPL
commit e475afc93c209a690c39c13a46716e8fa000c366
Author: Michael Liao <askxuefeng@gmail.com>
Date: Fri May 18 21:03:36 2018 +0800
add distributed
commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0
Author: Michael Liao <askxuefeng@gmail.com>
Date: Fri May 18 20:59:18 2018 +0800
wrote a readme file
git log
命令显示从最近到最远的提交日志,我们可以看到3次提交,最近的一次是append GPL
,上一次是add distributed
,最早的一次是wrote a readme file
。
如果嫌输出信息太多,可以加上--pretty=oneline
参数:
$ git log --pretty=oneline
1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master) append GPL
e475afc93c209a690c39c13a46716e8fa000c366 add distributed
eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 wrote a readme file
上面一大串类似
1094adb...
的是commit id
(版本号),它是一个SHA1计算出来的一个非常大的数字,因为Git是分布式的版本控制系统,在工作中可能多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号就会发生冲突。
那么如何把 readme.txt 回退到上一个版本,即add distributed
版本呢?
首先,Git必须知道当前版本是哪个版本,在Git中用HEAD
表示当前版本,也就是最新的提交1094adb...
,上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
。
要把当前版本append GPL
回退到上一个版本add distributed
,就可以使用git reset
命令:
$ git reset --hard HEAD^
HEAD is now at e475afc add distributed
查看 readme.txt 的内容可以发现已经回到上一个版本了。
$ cat readme.txt
Git is a distributed version control system.
Git is free software.
用git log
再看看现在版本库的状态:
$ git log
commit e475afc93c209a690c39c13a46716e8fa000c366 (HEAD -> master)
Author: Michael Liao <askxuefeng@gmail.com>
Date: Fri May 18 21:03:36 2018 +0800
add distributed
commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0
Author: Michael Liao <askxuefeng@gmail.com>
Date: Fri May 18 20:59:18 2018 +0800
wrote a readme file
会发现最新的那个版本append GPL
不见了,好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,怎么办?其实只要上面的命令行窗口还没有被关掉,你就可以找到append GPL
的commit id
,然后回到未来的版本:
$ git reset --hard 1094a
HEAD is now at 83b0afe append GPL
版本号写前几位就可以,Git会自动去找(不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个)
再次查看 readme.txt 的内容,发现果然回来了。
$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
假如你回退到了某个版本,关掉了电脑,第二天想恢复到新版本但找不到新版本的commit id
怎么办?
Git提供了一个命令git reflog
用来记录你的每一次命令:
$ git reflog
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file
可以看到append GPL
的commit id是1094adb
,现在又可以回到新版本了。
Git的版本回退速度之所以快,是因为Git在内部有个指向当前版本的
HEAD
指针,当回退版本的时候,Git仅仅是把HEAD从指向append GPL
改为指向add distributed
,顺便把工作区的文件更新了。
┌────┐
│HEAD│
└────┘
│
└──> ○ append GPL
│
○ add distributed
│
○ wrote a readme file
--------------------------------------
# 改为指向 add distributed
┌────┐
│HEAD│
└────┘
│
│ ○ append GPL
│ │
└──> ○ add distributed
│
○ wrote a readme file
二、基础知识
2.1 工作区和暂存区
Git和其他版本控制系统的不同之处就是有暂存区的概念。暂存区是Git非常重要的概念,弄明白了暂存区,就弄明白了Git的很多操作到底干了什么。
-
工作区(Working Directory)
工作区就是你在电脑里能看到的目录,比如我的 test 文件夹就是一个工作区。 -
版本库(Repository)
工作区有一个隐藏目录.git
,这是Git的版本库,不算工作区。版本库里存了很多东西,其中最重要的就是称为stage的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
前面讲了向Git版本库里提交文件分两步执行:
- 用
git add
把文件添加进去,实际上就是把文件修改添加到暂存区; - 用
git commit
提交更改,实际上就是把暂存区的内容提交到当前分支。
简单理解为,需要提交的文件修改全部放到暂存区,然后,一次性提交暂存区的所有修改。因为我们创建Git版本库时,Git自动为我们创建了一个master
分支,所以git commit
就是往master
分支上提交更改。
下面开始实战,先对 readme.txt 加上一行内容,然后再新建一个LICENSE
文本文件。
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
先用git status
查看一下状态:
$ git status
On branch master
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: readme.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
LICENSE
no changes added to commit (use "git add" and/or "git commit -a")
Git告诉我们 readme.txt 被修改了,而 LICENSE 还从来没有被添加过,所以它的状态是Untracked
。
现在把readme.txt
和LICENSE
都添加后,再查看一下状态:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: LICENSE
modified: readme.txt
现在,暂存区的状态就变成这样了:
所以,git add
命令就是把要提交的修改放到暂存区(Stage),然后执行git commit
就可以一次性把暂存区的所有修改提交到分支。提交后如果没有对工作区做任何修改,那么工作区就是“干净”的。
$ git status
On branch master
nothing to commit, working tree clean
现在版本库变成了这样,暂存区就没有任何内容了:
2.2 管理修改
在Git中是对修改进行跟踪并管理,而非文件。比如新增或删除了一行,更改了某些字符,甚至创建一个新文件,这都算是修改,都会被Git跟踪并管理。下面通过实验来说明。
第一步,为 readme.txt 加一行内容:
$ cat readme.txt
Git is a distributed version control system.
Git tracks changes.
然后添加到暂存区:
$ git add readme.txt
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
然后再修改 readme.txt:
$ cat readme.txt
Git is a distributed version control system.
Git tracks changes of files.
提交:
$ git commit -m "git tracks changes"
[master 519219b] git tracks changes
1 file changed, 1 insertion(+)
提交后,再看看状态:
$ git status
On branch master
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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
会发现第一次修改被提交了,而第二次的修改没有被提交。这是因为Git管理的是修改,当你用git add
命令后,在工作区的第一次修改被放入暂存区,准备提交,但是第二次修改并没有放入暂存区,所以,git commit
只负责把暂存区的修改提交了。
2.3 撤销修改
假如你在readme.txt
中添加了一行内容,在提交前你又删掉这一行内容,手动把文件恢复到上一个版本的状态。用git status
查看一下:
$ git status
On branch master
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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
可以发现Git会告诉你,git checkout -- file
可以丢弃工作区的修改:
$ git checkout -- readme.txt
命令git checkout -- readme.txt
意思就是,把 readme.txt 文件在工作区的修改全部撤销,这里有两种情况:
-
readme.txt 自修改后还没有被放到暂存区,现在撤销修改就回到和版本库一模一样的状态;
-
readme.txt 已经添加到暂存区后,又作了修改,现在撤销修改就回到添加到暂存区后的状态。
总之就是让文件回到最近一次git commit
或git add
时的状态。
git checkout -- file
命令中的--
很重要,没有--
,就变成了 “切换分支” 的命令。
假如已经把错误代码git add
到暂存区了,但在commit
之前发现了这个问题。用git status
查看一下,修改只是添加到了暂存区,还没有提交:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
Git告诉我们,用命令git reset HEAD <file>
可以把暂存区的修改撤销掉(unstage),重新放回工作区:
$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
git reset
命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD
时,表示最新的版本。
查看一下状态,现在暂存区是干净的,工作区有修改:
$ git status
On branch master
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: readme.txt
2.4 删除文件
在Git中删除也算修改操作,例如新建一个文件test.txt
并提交:
$ git add test.txt
$ git commit -m "add test.txt"
[master b84166e] add test.txt
1 file changed, 1 insertion(+)
create mode 100644 test.txt
然后再把 test.txt 文件删除,这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,使用git status
命令查看哪些文件被删除了:
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm
删掉,并且git commit
:
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master d46f35e] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
现在,文件就从版本库中被删除了。
先手动删除文件,然后使用
git rm <file>
和git add<file>
效果是一样的。
另一种情况就是误删了,但是版本库里还存有该文件,可以用git checkout
命令把误删的文件恢复到最新版本。
$ git checkout -- test.txt
三、远程仓库
Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。这就需要一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。而这个服务器就是GitHub,这个网站就是提供Git仓库托管服务的,所以只需注册一个GitHub账号,就可以免费获得Git远程仓库。
3.1 设置ssh
由于本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以要先设置好ssh。
第1步: 创建SSH Key
在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa
和id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
$ ssh-keygen -t rsa -C "youremail@example.com"
然后一路回车即可。完成后可以看到.ssh
目录里有id_rsa
和id_rsa.pub
两个文件,这两个就是SSH Key的秘钥对,id_rsa
是私钥,不能泄露出去,id_rsa.pub
是公钥,可以放心地告诉任何人。
第2步: 登陆GitHub,打开“Account settings”,“SSH Keys”页面,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub
文件的内容:
点“Add Key”,可以看到Key已经添加:
GitHub通过SSH Key来识别是你推送的还是别人冒充的,GitHub还支持添加多个Key。假定你有两台电脑,分别在公司和家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
3.2 添加远程库
现在在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作。
首先登陆GitHub,创建一个名为 test 的新仓库:
现在GitHub上的test
仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后把本地仓库的内容推送到GitHub仓库。
现在根据GitHub的提示,在本地的test
仓库下运行命令:
$ git remote add origin git@github.com:yangyezhuang/test.git
添加后,远程库的名字就是origin
,这是Git默认的叫法,也可以改成别的。
之后用git push
命令把本地库的内容推送到远程,即把当前分支master
推送到远程。
$ git push -u origin master
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (20/20), 1.64 KiB | 560.00 KiB/s, done.
Total 20 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:michaelliao/learngit.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
由于远程库是空的,我们第一次推送master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令。
推送成功后可以看到远程库的内容已经和本地一模一样。至此,只要本地作了提交,就可以把本地master
分支的最新修改推送至GitHub
$ git push origin master
删除远程库
如果添加的时候地址写错了,或者就是想删除远程库,可以用git remote rm <name>
命令。使用前,建议先用git remote -v
查看远程库信息:
$ git remote -v
origin git@github.com:michaelliao/learn-git.git (fetch)
origin git@github.com:michaelliao/learn-git.git (push)
然后根据名字删除,比如删除origin
:
$ git remote rm origin
此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。
3.3 克隆远程库
登陆GitHub,找到你想要克隆的仓库,可以看到GitHub给出了ssh
地址,复制该地址。
使用git clone
命令克隆一个本地库:
$ git clone git@github.com:yangyezhuang/Matplotlib.git
Cloning into 'gitskills'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
Receiving objects: 100% (3/3), done.
然后进入Matplotlib
目录可以看到,项目已经克隆到本地了。
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。
你也许还注意到,GitHub给出的地址不止一个,还可以用https://github.com/yangyezhuang/Matplotlib.git
这样的地址。Git支持多种协议,默认的git://
使用ssh,但也可以使用https
等其他协议。使用https
除了速度慢以外,每次推送都必须输入口令。
四、分支管理
4.1 创建与合并分支
什么是分支?有了分支,你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,既安全,又不影响别人工作。合并后再删掉分支,这和直接在
master
分支上工作效果是一样的,但过程更安全。
Git会把每次提交串成一条时间线,这条时间线就是一个分支。git init
时Git会自动创建一个master
分支,即主分支。HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
当创建新的dev
分支时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:
从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。其实就是直接把master
指向dev
的当前提交:
合并完分支后也可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后就剩下一条master
分支:
下面开始实战:
首先创建一个 dev 分支,用git checkout -b
命令创建并切换分支,相当于git branch dev
+ git checkout dev
。
$ git checkout -b dev
Switched to a new branch 'dev'
然后用git branch
命令查看当前分支,该命令会列出所有分支,当前分支前面会标一个*
号。
$ git branch
* dev
master
现在就可以在dev
分支上开发与提交了,对 readme.txt 加上一行,然后提交:
$ git cat readme.txt
Creating a new branch is quick.
$ git add readme.txt
$ git commit -m "branch test"
[dev b17d20e] branch test
1 file changed, 1 insertion(+)
dev
分支的工作完成,现在切换回master
分支:
$ git checkout master
Switched to branch 'master'
切换回master
分支后,查看readme.txt
文件发现刚才添加的内容不见了,因为那个提交是在dev
分支上,而master
分支此刻的提交点并没有变:
现在用git merge
命令把dev
分支的内容合并到master
分支上:
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
合并后查看 readme.txt 发现和dev
分支的最新提交是一样的。现在就可以删除dev
分支了
注意:上面的
Fast-forward
信息,意思是,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。
因为切换分支使用git checkout <branch>
,撤销修改则是git checkout -- <file>
,有点令人迷惑。因此,新版本的Git提供了新的git switch
命令来切换分支。
# 创建并切换到新的分支:
$ git switch -c dev
# 直接切换到已有分支:
$ git switch master
4.2 删除分支
开发中每添加一个新功能,最好新建一个分支开发,完成后,合并,最后删除该分支。
Git中使用git branch -d <file>
命令删除分支:
如果commit
提交了,但是还没有合并,这时删除分支的话Git会报错:
$ git branch -d test
error: The branch 'test' is not fully merged.
If you are sure you want to delete it, run 'git branch -D test'.
提示test
分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的-D
参数。
$ git branch -D test
Deleted branch test (was 287773e).
4.3 分支冲突
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
准备新的feature1
分支:
$ git switch -c feature1
Switched to a new branch 'feature1'
修改 readme.txt,在feature1
分支上提交:
$ git cat readme.txt
Creating a new branch is quick AND simple.
$ git add readme.txt
$ git commit -m "AND simple"
[feature1 14096d0] AND simple
1 file changed, 1 insertion(+), 1 deletion(-)
切换到master
分支,在master
分支上也对 readme.txt 进行修改,提交:
$ git cat readme.txt
Creating a new branch is quick & simple.
$ git add readme.txt
$ git commit -m "& simple"
[master 5dc6824] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
现在,master
分支和feature1
分支各自都分别有新的提交,变成了这样:
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
通过输出可以看到,readme.txt 文件存在冲突,必须手动解决冲突后再提交。git status
也可以告诉我们冲突的文件:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
可以直接查看 readme.txt的内容:
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
可以看到,Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们需要手动修改后再进行提交:
Creating a new branch is quick and simple.
再提交,最后删除feature1
分支:
$ git add readme.txt
$ git commit -m "conflict fixed"
[master cf810e4] conflict fixed
$ git branch -d feature1
Deleted branch feature1 (was 14096d0).
现在,master
分支和feature1
分支变成了下图所示:
用带参数的git log
也可以看到分支的合并情况:
$ git log --graph --pretty=oneline --abbrev-commit
* cf810e4 (HEAD -> master) conflict fixed
|\
| * 14096d0 (feature1) AND simple
* | 5dc6824 & simple
|/
* b17d20e branch test
* d46f35e (origin/master) remove test.txt
* b84166e add test.txt
* 519219b git tracks changes
* e43a48b understand how stage works
* 1094adb append GPL
* e475afc add distributed
* eaadf4e wrote a readme file
4.4 分支管理策略
通常合并分支时,Git会用
Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。如果要强制禁用Fast forward
模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
合并分支时,加上--no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward
合并就看不出来曾经做过合并。下面实战一下--no-ff
方式的git merge
:
首先创建并切换dev
分支:
$ git switch -c dev
Switched to a new branch 'dev'
修改readme.txt文件,并提交一个新的commit:
$ git add readme.txt
$ git commit -m "add merge"
[dev f52c633] add merge
1 file changed, 1 insertion(+)
现在切换回master
:
$ git switch master
Switched to branch 'master'
准备合并dev
分支,请注意--no-ff
参数,表示禁用Fast forward
:
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
因为本次合并要创建一个新的commit,所以加上-m
参数,把commit描述写进去。合并后用git log
看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* e1e9c68 (HEAD -> master) merge with no-ff
|\
| * f52c633 (dev) add merge
|/
* cf810e4 conflict fixed
...
可以看到,不使用Fast forward
模式,merge后就像这样:
在实际开发中,master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活,干活都在dev
分支上。
4.5 Bug分支
开发中会经常遇到bug需要修复,修复bug时,我们可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
当你接到一个修复bug的任务时,当你准备创建一个分支issue-101
来修复它时,突然想起来,当前正在dev
上进行的工作还没有提交,Git提供了一个stash
功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge
现在用git status
查看工作区发现是干净的,因此可以放心地创建分支来修复bug。
首先确定要在哪个分支上修复bug,假定需要在master
分支上修复,就从master
创建临时分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git checkout -b issue-101
Switched to a new branch 'issue-101'
现在在issue-101
分支上将bug修复,然后commit
提交,然后切换到master
分支,并完成合并,最后删除issue-101
分支:
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git merge --no-ff -m "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
现在回到dev
分支继续干活,
$ git switch dev
Switched to branch 'dev'
$ git status
On branch dev
nothing to commit, working tree clean
工作区是干净的,刚才的工作现场存到哪去了?用git stash list
命令看看:
$ git stash list
stash@{0}: WIP on dev: f52c633 add merge
工作现场还在,Git把stash内容存在某个地方了,有两个办法可以恢复:
-
用
git stash apply
恢复,但是恢复后,stash内容并不删除,需要用git stash drop
删除。 -
用
git stash pop
,恢复的同时把stash内容也删了。
$ git stash pop
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: hello.py
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: readme.txt
Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)
再用git stash list
查看,就看不到任何stash内容了。你可以多次stash,恢复的时候,先用git stash list
查看,然后恢复指定的stash,用命令:
$ git stash apply stash@{0}
在master分支上修复了bug后,我们要想一想,dev分支是早期从master分支分出来的,所以,这个bug其实在当前dev分支上也存在。
那怎么在dev分支上修复同样的bug?重复操作一次?但是Git提供了更简单的方法,同样的bug,要在dev上修复,我们只需要把4c805e2 fix bug 101
这个提交所做的修改“复制”到dev分支。注意:我们只想复制4c805e2 fix bug 101
这个提交所做的修改,并不是把整个master分支merge过来。
Git提供了一个cherry-pick
命令,让我们能复制一个特定的提交到当前分支:
$ git branch
* dev
master
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
Git自动给dev分支做了一次提交,注意这次提交的commit是1d4b803
,它并不同于master的4c805e2
,因为这两个commit只是改动相同,但确实是两个不同的commit。用git cherry-pick
,我们就不需要在dev分支上手动再把修bug的过程重复一遍。当然也可以直接在dev分支上修复bug,然后在master分支上“重放”。
五、总结
-
使用命令
git init
,初始化Git仓库 -
使用命令
git add <file>
,可反复多次使用,添加多个文件 -
使用命令
git commit -m <message>
完成提交 -
使用命令
git status
,查看工作区状态 -
如果
git status
告诉你有文件被修改过,用git diff
可以查看修改内容 -
HEAD
指向当前版本,使用命令git reset --hard commit_id
可以切换版本 -
穿梭前,用
git log
可以查看提交历史,以便确定要回退到哪个版本 -
要重返未来,用
git reflog
查看命令历史,以便确定要回到未来的哪个版本 -
每次修改,如果不用
git add
到暂存区,那就不会加入到commit
中。 -
当改乱了某个文件的内容,想丢弃工作区的修改时,用命令
git checkout -- file
。 -
当改乱了某个文件的内容并添加到了暂存区时,想丢弃修改,分两步,第一步用命令
git reset HEAD <file>
,就回到了场景1,第二步按场景1操作。 -
假设把错的文件从暂存区提交到了版本库,想要撤销本次提交,只能进行版本回退。
-
命令
git rm
用于删除版本库里的文件。如果不小心误删了,但是文件已经提交到版本库,那么就可以从版本库将文件进行还原,但是只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。 -
要关联一个远程库,使用命令
git remote add origin git@server-name:path/repo-name.git
; -
关联一个远程库时必须给远程库指定一个名字,
origin
是默认习惯命名; -
关联后,使用命令
git push -u origin master
第一次推送master分支的所有内容;此后,每次本地提交后,就可以使用命令git push origin master
推送最新修改; -
使用
git clone
命令将远程仓库克隆到本地。 -
Git支持多种协议,包括
https
,但ssh
协议速度最快。 -
查看分支:
git branch
-
创建分支:
git branch <name>
-
切换分支:
git checkout <name>
或git switch <name>
-
创建+切换分支:
git checkout -b <name>
或git switch -c <name>
-
合并某分支到当前分支:
git merge <name>
-
删除分支:
git branch -d <name>
(-D
强行删除) -
查看分支合并图:
git log --graph
-
使用
git stash
可以将当前内容“储藏”起来,然后去修复bug,修复后,再git stash pop
,回到工作现场。 -
在master分支上修复的bug,想要合并到当前dev分支,可以用
git cherry-pick <commit>
把bug提交的修改“复制”到当前分支,避免重复劳动。
感谢大家的耐心阅读,如有建议请私信或评论留言