Git使用教程

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添加文件需要addcommit一共两步呢?因为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 GPLcommit 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版本库里提交文件分两步执行:

  1. git add把文件添加进去,实际上就是把文件修改添加到暂存区;
  2. 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.txtLICENSE都添加后,再查看一下状态:

$ 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 文件在工作区的修改全部撤销,这里有两种情况:

  1. readme.txt 自修改后还没有被放到暂存区,现在撤销修改就回到和版本库一模一样的状态;

  2. readme.txt 已经添加到暂存区后,又作了修改,现在撤销修改就回到添加到暂存区后的状态。

总之就是让文件回到最近一次git commitgit 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_rsaid_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:

$ ssh-keygen -t rsa -C "youremail@example.com" 

然后一路回车即可。完成后可以看到.ssh目录里有id_rsaid_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严格来说不是指向提交,而是指向mastermaster才是指向提交的,所以,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提交的修改“复制”到当前分支,避免重复劳动。

感谢大家的耐心阅读,如有建议请私信或评论留言

posted @ 2022-11-16 18:05  杨业壮  阅读(209)  评论(0编辑  收藏  举报