git基础学习笔记

一个由浅入深,学完后能立刻上手的Git教程,对于初学者来说,有一定的参考价值。

一、基本概念

1. 什么是Git

Git是一个开源的分布式版本控制系统。

Git的分布式:Git采用了分布式版本库的方式,不需要服务器端软件支持即可在本地完成版本控制工作。

Git的版本控制:是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。

Git的最基本理解:简单的说Git就是用于保存文件在每次修改时的快照的一个管理系统,这些快照构成了一个个可以来回切换的版本。所以你可以通过使用git来切换快照实现文件恢复,这使得我们管理大型项目代码或者文件时得到了安全的保障机制。当然git的功能不只是快照的来回切换那么简单。它竟然是一种系统,那必然有着很多其它管理性的功能,如分支合并(或者说是快照合并)、各版本文件差异对照、本地仓库和远程仓库的连接和互动、从本地库推送到远程库,从远程库克隆到本地等等。

2. git的文件管理机制

git把数据看作是小型文件系统的一组快照。每次提交更新时git都会对当前的全部文件制作一个快照并保存这个快照的索引。为了高效,如果文件没有修改,git不再重新存该文件,而是只保留一个链接指针指向之前存储的文件。所以git的工作方式可以称之为快照流。

3. git的工作流程

  • (1)在工作区中添加、修改文件
  • (2)将需要进行版本管理的文件存入暂存区
  • (3)将暂存区的文件提交到git仓库

4. git版本控制区域的情况

(1)工作区:就是你在电脑里能看到的目录。

(2)暂存区:英文叫stage, 或index。一般存放在 “.git目录下” 下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。

(3)版本库:工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。

1539864917453

5. git管理下文件的状态

  • (1)已修改(modified)
  • (2)已暂存(staged)
  • (3)已提交(committed)

二. 安装Git

在使用 Git 前需要先安装 Git。Git 目前支持 Linux/Unix、Solaris、Mac和 Windows 平台上运行。

Git 各平台安装包下载地址为:http://git-scm.com/downloads

安装的过程就是傻瓜式下一步。然后打开 git bash 输入相关 git 命令即可执行版本控制操作。

💁‍♂ Git GUI 图形化操作工具

安装 Git 后,主要是可以通过终端命令行方式进行 Git 操作,如果希望使用图形化的 Git 操作方式,可以选择以下工具:

  • (1)Git 内置的默认 Git 图形化操作工具:git gui (windows 对应文件路径<git安装路径>/cmd/git-gui.exe
  • (2)第三方 Git 图形化操作工具:
  • Sourcetree(不开源但免费,支持 Windows、Mac(不支持 Linux),除了支持 Git 外,还支持 Mercurial 等其他版本控制)
  • GitKraKen(不开源且收费,支持 Windows、Mac、Linux)
  • GitHub Desktop(开源免费,官方仅支持 Windows、Mac,但社区分支有 Linux 发行版
  • GitAhead(开源免费,支持 Windows、Mac、Linux)
  • GitCola(开源免费,支持 Windows、Mac、Linux)
  • TortoiseGit(开源免费,仅支持 Windows)
  • GitExtensions(开源免费,支持 Windows、Mac、Linux)

对于众多 Git GUI 工具的选择,个人推荐使用 Sourcetree,因为其较为主流,且美观好用。

参考:https://blog.csdn.net/mzl87/article/details/128577959

三、初始化git版本仓库

1
git init 		 #初始化git版本库,会自动创建了唯一master分支,并进入此分支

案例:

1
2
3
mkdir project  #创建文件目录
cd project #进入工作目录中
git init #初始化git版本库,会自动创建了唯一master分支,并进入此分支

project是所谓的工作区,工作区有一个隐藏目录.git,这个不算工作区,而是git的版本库。

git的版本库里存了很多文件,其中其中.git/index就是所谓的暂存区,即stage(或者叫index),它是一个二进制文件,还有指向master分支的一个指针文件HEAD等。

四、给暂存区添加文件

1
git add files   #将files添加到暂存区

案例:

1
2
echo "demo" >> demo.txt #创建一个内容为“demo”的测试文件
git add demo.txt #将demo.txt添加到暂存区

五、给git版本库提交文件

1
git commit -m "提交说明"  #一次性将添加到暂存区的所有文件提交到版本库中的master分支,-m后的信息是提交说明

案例:

1
git commit -m "commit demo.txt" #一次性将添加到暂存区的所有文件提交到版本库中的master分支,-m后的信息是提交说明

六、查看git文件状态

1
2
git status 	 #查看项目的当前状态信息。"AM" 状态的意思是,这个文件在我们将它添加到缓存之后又有改动。
git status -s #加了-s 参数,以获得简短项目的当前状态信息。

案例:

以下将从无文件改动或者添加---->创建文件----->添加文件到暂存区----->提交暂存区的所有文件到git版本库这几个不同阶段的文件状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#无文件改动或者添加的情况查看git状态:
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)



#创建文件并查看git状态:
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ echo "demo" >>demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git status
On branch master

No commits yet

Untracked files: #提示文件未被git跟踪,即此文件还没开始正式接受git的版本控制,一旦提交到暂存区开始到git版本库,就受到git的跟踪和版本控制。
(use "git add <file>..." to include in what will be committed)

demo.txt

nothing added to commit but untracked files present (use "git add" to track)

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)


#添加文件到暂存区并查看git状态:
#此时git开始跟踪提交的文件。
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git add demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git status
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)# 可以通过git rm --cached <file>清除提交的文件,git又不会跟踪文件了。或者通过git rm --cached * 清空暂存区的所以文件

new file: demo.txt



#提交暂存区的所有文件到git版本库并查看git状态:
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git commit -m "commit demo.txt"
[master (root-commit) 0e22aaf] commit demo.txt
1 file changed, 1 insertion(+)
create mode 100644 demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git status
On branch master
nothing to commit, working tree clean #此时提示没有暂存区里没有任何文件需要提交到git版本库了

img

七、查看历史提交记录

git的提交对象:文件对象存放在树对象里,树对象又存放在提交对象里。所以查看提交时的提交id值实际是所有提交文件组合而成的某种形式的哈希值。

1540408480201

1
2
3
4
5
git log  		#输出完整历史提交记录
git log --pretty=oneline #每次提交以一行输出历史提交记录
git log --oneline #每次提交以一行输出简短型历史提交记录
git log --decorate --all --oneline --graph #按分支情况查看提交记录
git reflog #查看历史所以commit id,并且能够显示到某个版本的步数,如:HEAD@{2}

注意:当提交记录后,git是无法再删除提交记录的(即版本仓库快照),除非删除了git仓库(.git目录),如果想要实现类似删除的效果只能通过移动HEDA指针的指向来实现。

案例:

以下就以修改demo.txt为例,进行历史提交记录查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git log
commit 0e22aafab1b3951a989105af18d3028b0bd6dc81 (HEAD -> master) #版本快照的id号,id值是通过哈希算法计算出来的哈希值,文件内容不同,哈希值必定不同。
Author: qcmoke <[email protected]>
Date: Thu Oct 18 21:47:18 2018 +0800

commit demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ vim demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git add demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git commit -m "modify demo.txt"
[master c3f6e58] modify demo.txt
1 file changed, 1 insertion(+), 1 deletion(-)

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git log
commit c3f6e587497f49870ab974ca4433a8e13467088d (HEAD -> master)
Author: qcmoke <[email protected]>
Date: Fri Oct 19 00:11:11 2018 +0800

modify demo.txt

commit 0e22aafab1b3951a989105af18d3028b0bd6dc81
Author: qcmoke <[email protected]>
Date: Thu Oct 18 21:47:18 2018 +0800

commit demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)

八、版本快照回滚操作

1539879799935

为了效果,在以上已经创建并修改了demo.txt的基础上,下面继续添加readme.txt文件,并对之进行一次修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#创建一个readme.txt文件,内容为"hello world !"
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ echo "hello world !" >> readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git add readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git commit -m "commit readme.txt"
[master 5b144ee] commit readme.txt
1 file changed, 1 insertion(+)
create mode 100644 readme.txt

#修改内容,向文件里添加"I am a student !"字符串
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ echo "I am a student !" >> readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git add readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git commit -m "modify readme.txt"
[master a10b617] modify readme.txt
1 file changed, 1 insertion(+), 1 deletion(-)



#查看历史提交记录
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git log
commit a10b617a02aa30ddd4e895d0653d33c7a28dcfad (HEAD -> master)
Author: qcmoke <[email protected]>
Date: Sat Oct 20 20:09:17 2018 +0800

modify readme.txt

commit 5b144ee3e670f710091d7441bcde01ceaf8c622e
Author: qcmoke <[email protected]>
Date: Sat Oct 20 20:06:38 2018 +0800

commit readme.txt

commit c3f6e587497f49870ab974ca4433a8e13467088d
Author: qcmoke <[email protected]>
Date: Fri Oct 19 00:11:11 2018 +0800

modify demo.txt

commit 0e22aafab1b3951a989105af18d3028b0bd6dc81
Author: qcmoke <[email protected]>
Date: Thu Oct 18 21:47:18 2018 +0800

commit demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)

以上操作完成后当前的情况如下图:

1540444596041

1. 回滚到上一个版本快照

1
git reset HEAD~   # 回滚到上个版本并且暂存区会被回滚到的仓库版本文件所覆盖。~表示上一个版本快照,~~表示上上个快照,以此类推,也可以用数字代替,如~10则表示前10个的版本
  • git reset --mixed HEAD~

    • 默认选项

    • 移动HEAD的指向,将其指向上一个版本快照

    • 将HEAD移动后指向的快照回滚到暂存区(暂存区会被回滚到的仓库版本文件所覆盖)

  • git reset --soft HEAD~

    • 移动HEAD的指向,将其指向上一个快照(不回滚到暂存区)
  • git reset --hard HEAD~

    • 移动HEAD的指向,将其指向上一个版本快照
    • 将HEAD移动后指向的快照回滚到暂存区
    • 将暂存区的文件还原到工作目录(工作目录会被暂存区的文件所覆盖)

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git reset HEAD~
Unstaged changes after reset:
M readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git log
commit 5b144ee3e670f710091d7441bcde01ceaf8c622e (HEAD -> master)
Author: qcmoke <[email protected]>
Date: Sat Oct 20 20:06:38 2018 +0800

commit readme.txt

commit c3f6e587497f49870ab974ca4433a8e13467088d
Author: qcmoke <[email protected]>
Date: Fri Oct 19 00:11:11 2018 +0800

modify demo.txt

commit 0e22aafab1b3951a989105af18d3028b0bd6dc81
Author: qcmoke <[email protected]>
Date: Thu Oct 18 21:47:18 2018 +0800

commit demo.txt
#可以发现记录里没有“modify readme.txt”的内容了,因为已经回滚到了上一个版本,而上一个版本并没有这条提交记录。


wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ 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提示要向暂存区添加文件,并提交文件到版本库。原因是执行回滚后工作区的readme.txt要比暂存区的readme.txt新,相对于工作区对readme.txt做了修改。

1540040885722

2. 回滚到特定版本快照

1
git reset 版本快照的id号

案例:

1
2
3
4
如本例中要从第四个版本回滚到第三个版本:
git reset c3f6e587497f49870ab974ca4433a8e13467088d
或者简写(能识别即可):
git reset c3f6e58

3. 回滚快照中的个别文件

1
git reset 版本快照  文件名/文件路径   #HEAD指针不移动,只回滚个别文件

4. 往新版本回滚

类似的只要记住版本快照的id号即可往往新版本回滚。

1
git reset 版本快照的id号

但往旧版本回滚后并且关闭了shell,如果在回退以后又想再次回到之前的版本,用git log会查不到版本id号。可以通过以下命令查看所有commit记录。

1
git reflog   #查看历史所以commit id

九、恢复工作区

1. 没有add的情况

1
2
3
4
5
#检出,只能清空全部已修改的问题件, 但是对于新建的文件和文件夹无法清空, 必须组合下面命令。如果只作用个别文件用参数 -- <file>
git checkout .

#清空所有新建的文件和文件夹
git clean -df

git clean的参数说明:

-f :删除 一些 没有 git add 的 文件
-df:删除 一些 没有 git add 的 文件和目录
-n:显示将要删除的文件或者目录

2. 已经add但没有commit的情况

1
2
3
git reset . #重置,覆盖暂存,但不覆盖本地工作区
git checkout .
git clean -df

注: 这种情git reset不允许使用--soft和--hard选项

3. 已经add并且commit的情况

1
git reset --hard HEAD~ #重置,覆盖暂存和本地工作区

十、checkoutreset的区别

  • 恢复文件

    当用checkoutreset来恢复指定快照中的指定文件时,两种的命令都不会改变HEAD指针的指向。

    他们的区别是:checkout命令会同时覆盖暂存区和工作区;而reset命令默认只是将指定快照的指定文件恢复到暂存区(--mixed)。

    注意:在恢复指定快照中的指定文件时使用git reset不允许使用--soft和--hard选项

  • 恢复快照

    当用checkoutreset来恢复指定快照时,两种的命令都会改变HEAD指针的指向。

    他们的区别是:checkout命令只移动HEAD自身指向其他分支,并不移动HEAD所在的分支指针;而reset命令会移动HEAD自身指向其他分支并且会移动HEAD所在的分支指针指向其他版本库快照。

十一、文件diff差异比较

1. 比较暂存区与工作区的文件差异

1
git diff file

案例:

我们对demo.txt的内容做以下修改。改成以下:

1
2
3
4
5
6
7
#include <iostream>
int main()
{
using namespace std;
cout << "Hello World !"<< endl;
return 0;
}

并添加到暂存区。

1
git add demo.txt

然后在工作区再对demo.txt做修改。修改为以下:

1
2
3
4
5
6
7
#include <iostream>
using namespace std;
int main()
{
cout << "Hello Git World !"<< endl;
return 0;
}
1
git diff demo.txt  #比较暂存区与工作区的demo.txt差异

打印的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
diff --git a/demo.txt b/demo.txt  #a/demo.txt是暂存区的demo.txt,b/demo.txt是工作区的
index ab28497..f6e1eb1 100644 #ab28497..f6e1eb1是文件id,100644是文件类型和权限
--- a/demo.txt #---表示旧文件
+++ b/demo.txt #+++表示新文件
@@ -2,6 +2,6 @@ #-表示旧文件,+表示新文件,数字表示开始行号和联连续的行数
using namespace std; #没有+或者-表示共有的内容
int main()
{
- cout << "Hello World !"<< endl; #-的内容表示旧文件所特有的内容
+ cout << "Hello Git World !"<< endl; #+的内容表示新文件所特有的内容
return 0;
}
\ No newline at end of file

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)

2. 比较两个历史快照的差异

1
git diff 快照id1  快照id2  file

3. 比较工作区和版本快照的差异

1
2
git diff 快照id file #工作区和具体的某个快照进行对比
git diff HEAD file #工作区和HEAD所指向的版本快照进行对比

4. 比较暂存区和版本快照的差异

1
2
git diff --cached file           #暂存区和HEAD所指向的版本快照进行对比
git diff --cached 版本快照id file #暂存区和具体的某个快照进行对比

十二、提交修改

需求:提交暂存区的所以文件到版本库后(如 git commit -m “commit all files as sunday !”),之后又改动了本地的某个文件,但不想因为此个例再另外提交一次而生成一个版本快照(即生成一个提交log),只想要让这个修改的文件添加到上次的提交中(当前最新的版本快照中)。

可以使用以下命令来实现。

1
git commit --amend  #提交暂存区的内容,但不产生新的版本快照,只是对原本的快照进行修改。

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git add demo.txt #先添加修改过的demo.txt到暂存区

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: demo.txt


wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git commit --amend #对当前最新的快照进行修改,使暂存区的demo.txt文件覆盖最新版本仓库快照中的demo.txt。
[master 319082e] commit readme.txt
Date: Sat Oct 20 20:06:38 2018 +0800
2 files changed, 8 insertions(+), 1 deletion(-)
create mode 100644 readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git status
On branch master
nothing to commit, working tree clean

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git log #发现没有另外生成一个新的版本仓库快照。
commit 319082ee50a29e9f33a69d084b5f8acd17ac5b09 (HEAD -> master)
Author: qcmoke <[email protected]>
Date: Sat Oct 20 20:06:38 2018 +0800

commit readme.txt

commit c3f6e587497f49870ab974ca4433a8e13467088d
Author: qcmoke <[email protected]>
Date: Fri Oct 19 00:11:11 2018 +0800

modify demo.txt

commit 0e22aafab1b3951a989105af18d3028b0bd6dc81
Author: qcmoke <[email protected]>
Date: Thu Oct 18 21:47:18 2018 +0800

commit demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)

十三、删除各区的文件

1
2
3
4
git rm 文件名  #只删除工作区和暂存区的该文件,git rm . 则表示清空工作区和暂存区的文件而不是指定的单个文件
git rm -f 文件名 #当工作区和暂存区的内容不一样时,执行此命令来强制删除工作区和暂存区的该文件
git rm -cached 文件名 #只删除暂存区的文件,但保留工作区的文件。
git reset--hard HEAD~ #如果要删除文件同时要清除记录,可以采取回滚的方法

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ ls
demo.txt readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git rm demo.txt #删除工作区和暂存区的demo.txt
rm 'demo.txt'

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ ls
readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git status #暂存区中提示已经删除demo.txt
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

deleted: demo.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git commit -m "deleted demo.txt" #提交本次修改
[master 65ca766] deleted demo.txt
1 file changed, 7 deletions(-)
delete mode 100644 demo.txt

十四、重命名文件

1
2
git mv 旧文件名 新文件名  #重命名工作区的文件,暂存区的文件也被重命名
mv 旧文件名 新文件名 #只重命名工作区的文件,暂存区的文件没有重命名

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ ls
readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git mv readme.txt Readme.txt #将工作区和暂存区的readme.txt重命名为Readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ ls
Readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

renamed: readme.txt -> Readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project (master)
$ git commit -m "renamed readme.txt -> Readme.txt" #提交本次修改
[master 330b8d7] renamed readme.txt -> Readme.txt
1 file changed, 0 insertions(+), 0 deletions(-)
rename readme.txt => Readme.txt (100%)

十五、分支管理

1. 查看分支

1
2
3
4
5
6
#查看本地分支(这个命令会列出当前所有本地分支,其中前面带有 * 号的表示当前所在的分支。)
git branch
#查看远程分支
git branch -r
#查看本地和远程分支
git branch -a

2. 创建分支

初始化git仓库时,HEAD指针默认指向master分支。如果需要其他分支则需要创建和切换。用下面命令来实现此需求:

1
2
3
4
5
#创建分支
git branch <name>

#示例:
git branch dev #创建dev分支

3. 切换分支

1
2
3
4
5
6
7
8
#切换分支(将当前分支切换到另一个分支,并更新工作目录中的文件内容)
git checkout <name>

#示例1:
git branch dev #创建dev分支
git checkout dev #切换到dev分支
#示例2:
git checkout -b dev #创建dev分支并切换到dev分支

需要注意的是,在进行分支切换时,必须保证当前的工作目录和暂存区是干净的(没有未提交的变更),否则切换可能会失败。如果想要保存当前分支的修改,可以先执行 git addgit commit 命令将修改提交到当前分支,然后再执行切换分支的操作。

当我们创建新的分支并(如:dev)进行分支切换时,git会新建了一个指针叫dev,其指向master相同的版本仓库快照,当切换到dev分支时,就会把HEAD指向dev,就表示当前分支在dev上。

1540314110963

从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变。1540314143868

假如我们在dev上的工作完成了,就可以把dev合并到master上。最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:

1540317847182

合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉:

1540317908951

4. 合并分支

简单使用

1
2
#合并某分支到当前分支
git merge <name>

示例:合并dev分支到master分支。

1
2
3
4
#先切换到master分支(HEAD指针指向master) 
git checkout master
#合并dev到当前分支(即master分支)
git merge dev

对于以上案例的合并原理是:HEAD指向的master指针移动到了dev指针所指的节点即完成了合并。

1540405021121

合并冲突

多个开发者对同一版本的同一个文件进行修改并提交,当合并两个开发者的提交的分支时会出现合并冲突问题。

下面是合并冲突的一个简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
####(1)git项目初始化操作 ####
#初始化一个新目录为git仓库
git init test
#进入git仓库目录里(初始化的git仓库默认在master分支)
cd test/

#创建一个测试文件并输入内容
echo "init" > test.txt
#提交测试文件到默认的master分支
git add . && git commit -m "init"


####(2)产生冲突操作 ####
#新建dev分支
git branch dev
#切换到dev分支
git checkout dev
#修改测试文件内容
echo "dev" >> test.txt
#提交测试文件到dev分支
git add . && git commit -m "modify by dev"

#切换回和master分支
git checkout master
#修改测试文件内容
echo "master" >> test.txt
#提交测试文件到master分支
git add . && git commit -m "modify by master"

####(3)合并分支并解决冲突操作 ####
##此时dev分支和master分支的版本一样,但是测试文件内容不一样
#可以在master分支上合并dev分支过来,操作如下:
git merge dev

#此时的测试文件内容如下:
$ cat test.txt
init
<<<<<<< HEAD
master
=======
dev
>>>>>>> dev

#编辑存在内容冲突的测试文件并解决冲突的内容
$ vi test.txt
#删除掉特殊符号并修改为需要的内容,修改后的内容如下:
init
master
dev

#提交已经修复冲突的测试文件到master分支(冲突解决)
git add . && git commit -m "merge dev and modify by master"


#由于某些原因需要放弃合并(如冲突难以解决),那么可以执行如下命令来撤销合并操作:
git merge --abort

image-20230514163309930

分析

(1)合并不会冲突情形:

在原分支上的某一原节点创建第二个分支,此时原分支和新创建的分支指向这个原节点。从这个原节点往第二个分支的方向开发(修改)得到新的节点(在第二个分支上),再将第二个分支的新节点合并到原节点(在原分支上),这种合并不会发生冲突。
理解:“合并节点的快照”比“被合并节点的快照”的版本新,合并不会冲突。

情况如图:

1540444441499

(2)合并会冲突情形:

同一节点分叉开发后合并到其中一个分支上会合并冲突。
在原分支上的某一原节点创建第二个分支,此时原分支和新创建的分支指向这个原节点,在原分支的这个原节点上开始修改得到新的节点。在第二个分支的这个原节点上修改得到另一个新的节点,此时合并一个分支到另一个分支都会冲突。
理解:“合并节点的快照”和“被合并节点的快照”的版本一样新,合并会冲突。

1540444461052

5. 删除分支

1
2
3
4
5
#删除分支
git branch -d <name>

#示例:
git branch -d dev #删除dev分支

十六、git远程管理

1. git服务端访问配置

1)简要概述

git客户端访问git服务端的仓库,需要通过远程仓库的地址进行访问,远程仓库的地址通常为HTTP协议地址或者SSH协议地址。

(1)HTTP协议地址

  • 地址格式:http://host:port/xxx.githttps://host:port/xxx.git

  • 鉴权与加密方式:通过用户名和密码或访问令牌等进行身份验证,加密通信则通过TLS/SSL来实现。

  • 秘钥存储:首次首次访问远程库时需输入了用户名和密码进行身份验证,之后会保存在系统的凭据存储区域中,各个平台的凭据存储机制如下:

  • Windows:Windows凭据管理器 (可在 控制面板>用户帐户>凭据管理器>Windows 凭据中查看,或通过cmd命令 cmdkey /list 查看)

  • macOS: 钥匙串(Keychain Access)

  • Linux:GNOME Keyring或KWallet

(2)SSH协议地址:

  • 地址格式:ssh://git@host:port/xxx.gitgit@host:xxx.git (如果SSH协议地址使用默认的22端口则可忽略ssh://和端口进行简写)

  • 鉴权与加密方式:使用SSH秘钥对进行身份验证和加密通信。

  • 秘钥存储:客户端SSH秘钥对存储在~/.ssh目录,目录下有私钥文件(即id_rsa)和公钥文件(即id_rsa.pub,含公钥密文,即所谓的SSH秘钥);服务端SSH秘钥对同样存储在~/.ssh目录下,另外服务端还有个重要的授权文件(即 authorized_keys),其存放有客户端的公钥密文,用于客户端访问的身份验证。

  • 身份验证过程:当用户尝试与SSH服务器建立连接时,服务器会要求客户端发送其公钥。如果该公钥存在于服务器上的authorized_keys文件中,则服务器将允许客户端访问并使用该SSH帐户。

2)SSH秘钥配置

(1)客户端配置
1
2
3
4
5
6
#生成ssh密钥对
ssh-keygen -t rsa -C "[email protected]"
#然后连续3次回车,最终会~/.ssh目录下生成私钥文件(id_rsa)和公钥文件(id_rsa.pub)

#获取客户端的ssh公钥密文(后续服务端配置用到)
cat ~/.ssh/id_rsa.pub

关于ssh-keygen命令行工具参数的说明:

  • -t: 指定要生成的密钥类型。支持的密钥类型有:rsa、dsa、ecdsa、ed25519等
  • -C: 可选,在密钥末尾添加注释信息以标识该密钥的用途(一般为用户的邮箱地址),仅仅为注释,有无都不会影响ssh加解密和身份验证。
  • -b: 指定密钥长度(以比特为单位)。默认情况下,RSA密钥的长度为2048位,DSA密钥的长度为1024位,ECDSA密钥的长度为256位,Ed25519密钥的长度为256位。
  • -f: 指定生成密钥的文件名和路径。如果不指定该参数,则默认生成id_rsa和id_rsa.pub两个密钥文件,并保存在~/.ssh目录下。
  • -N: 指定新密钥的密码短语(也称为口令)。
  • -i: 导入已有的密钥文件。
  • -e: 将OpenSSH格式的密钥转换为其他格式(如PKCS#8格式)。
  • -y: 提取公钥部分,输出到标准输出。
  • -R: 从known_hosts文件中删除指定主机的条目。
(2)服务端配置
1
2
3
4
5
#添加客户端的ssh公钥密文到服务端授权文件中,用于服务端的身份验证
echo "客户端的ssh公钥密文" >> ~/.ssh/authorized_keys

#例如:
echo "ssh-rsa AAAAB311/c/250YucGiB/mbZU= [email protected]" >> ~/.ssh/authorized_keys

此外还可以在客户端通过命令:ssh-copy-id -p port user@remote 将公钥密文自动上传保存到服务端授权文件authorized_keys中,不过执行该命令需要输入 “user” 用户的ssh密码。

服务端添加

(3)ssh访问身份验证测试

可在客户端执行如下命令来测试访问服务端时身份验证是否通过:

1
2
3
4
ssh -p port -T git@host
#例如:
ssh -T [email protected]
ssh -p 2222 -T [email protected]

3)客户端多SSH秘钥配置

在通过SSH协议访问Git服务端时,Git客户端是借助SSH来实现访问的。在默认情况下SSH会使用~/.ssh/id_rsa为默认私钥文件,并会自动使用该密钥进行身份验证。但如果一个客户端需要访问多个服务端,并且访问不同服务端对应的SSH秘钥也不同,那么客户端就需要生成不同的SSH秘钥对文件(如:id_rsa_gitlibid_rsa_gogs),操作如下:

1
2
3
4
#生成新的SSH秘钥对,并指定秘钥对的文件路径
ssh-keygen -t rsa -C "[email protected]" -f ~/.ssh/id_rsa_example #会在~/.ssh目录下生成私钥文件id_rsa_example和公钥文件id_rsa_example.pub
#将新的SSH公钥设置到服务端中
cat ~/.ssh/id_rsa_example.pub

对于客户端使用多个不同的SSH秘钥对文件,SSH无法自动识别到这些不同文件路径的私钥文件,故无法自动使用这些非默认的私钥文件进行身份验证,也就是说无论访问哪个服务器都会使用默认的私钥文件id_rsa进行身份验证,故而对于非默认秘钥配置的服务器会验证失败。解决这个问题有以下两种方式:

(1)使用新的ssh代理来添加自定义的私钥

1
2
3
4
5
6
7
8
#启动一个新的ssh代理
ssh-agent bash
#添加自定义的私钥到ssh代理中
ssh-add ~/.ssh/id_rsa_example
#查看所有添加秘钥列表
ssh-add -l
#删除所有新增的私钥
#ssh-del -l

💁‍♂ 关于SSH代理的说明:

SSH代理(SSH Agent)是一个程序,用于管理和存储SSH密钥,并在需要进行SSH身份验证时提供这些密钥。它允许SSH客户端与SSH密钥分离,并避免在每次连接到SSH服务器时都要输入密码或者手动加载密钥的繁琐性。

当您将SSH私钥添加到代理中后,代理会将私钥存储在内存中,并将其用于后续的SSH连接身份验证。这样,只要代理仍然在运行并且SSH密钥仍然存在于代理中,您就无需再次输入密码或加载密钥即可连接到SSH服务器。

另外,SSH代理还提供了一种安全方式来存储和管理多个SSH密钥。通过使用SSH代理,您可以轻松地切换不同的SSH密钥,并控制哪些密钥可供使用,以确保安全性和保护私人数据。

需要注意的是,在使用SSH代理时,如果您关闭了代理或重新启动计算机,则必须重新添加SSH密钥才能使用。此外,为了确保安全性,建议在使用完SSH密钥后从代理中清除它们,以避免恶意软件攻击或其他安全漏洞。

(2)SSH客户端的配置文件添加SSH登录配置

1
2
3
4
5
6
7
cat >> ~/.ssh/config << 'EOF'
Host *.example.com
HostName git.example.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa_example
User user1
EOF

配置文件参数

  • Host: 请求服务端时,服务端ip或域名的匹配模式(可含通配符,如:*.60.156*.example.com),可根据这个识别模式配置到主机名和私钥文件
  • HostName: 服务端的访问ip或域名(不能含通配符,如:192.168.60.156git.example.com),默认值为访问时ssh链接地址的ip或域名,如果使用内网穿透等反向代理服务器则可以修改
  • User: ssh用户名 (默认值为ssh链接地址里的用户名)
  • IdentityFile: 私钥文件路径
  • Port: 端口号(默认值为访问时ssh链接地址的端口, 如果使用内网穿透等反向代理服务器则可以修改)

对于git可使用如下最简写:

1
2
3
4
cat >> ~/.ssh/config << 'EOF'
Host 192.168.60.156
IdentityFile ~/.ssh/id_rsa_example
EOF

2. git提交用户信息配置

设置git提交代码时需要设置的作者名和电子邮件地址

使用Git时,如果未设置用户名和邮箱地址,则每次提交代码时都需要手动输入相关信息,这可能会非常繁琐和易错,所以一般在提交仓库分支时,这些信息都是需要提前配置的。

注意此处的提交代码的用户名并非ssh的用户名,其仅仅是标识提交代码时的作者名称,其可以根据需要进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#配置全局git用户信息(配置信息存储在 ~/.gitconfig 文件里)
git config --global user.name "username"
git config --global user.email "[email protected]"

#配置当前项目git用户信息(可为每git本地仓库设置,配置信息存储在当前项目的 .git/config 文件里 )
git config user.name "username"
git config user.email "[email protected]" 


#查看全局git用户信息
git config --global user.name
git config --global user.email
#也可以使用如下命令获取全局的所有配置信息
git config --global --list

#查看当前项目git用户信息(默认为全局git用户信息)
git config user.name
git config user.email
#也可以使用如下命令获取当前项目的所有配置信息
git config --list

3. 添加本仓库和远程库的关联

将本地库与远程库建立联系,并指定该远程存储库的名称。

1
2
3
4
5
6
git remote add 远程库名称 远程库地址

#例如:
git remote add origin https://host:port/xxx.git #使用HTTP协议地址
git remote add origin ssh://git@host:port/xxx.git #使用SSH协议地址
git remote add origin git@host:xxx.git #如果使用ssh协议使用默认的22端口则可如此简写

4. 解除本仓库和远程库的关联

1
2
3
4
git remote rm 远程库名称

#例如:解除本仓库和名称为origin的远程库的关联
git remote rm origin

注意,此命令是解除了本地仓库和远程仓库的关联,并不是删除了远程仓库的数据。

5. 修改本地库关联的远程库信息

1
2
3
4
5
#修改本地库所对应的新远程库地址。
git remote set-url 远程库名称 远程库地址

#例如
git remote set-url origin https://host:port/xxx.git

6. 查看本地库关联的远程库信息

1
2
3
4
#列出所有远程库名称
git remote
#列出所有远程库名称和地址
git remote -v

7.设置本地分支的上游分支

即设置本地库的指定分支关联到远程库的指定分支。

1
2
3
4
5
6
7
#origin/remote_branch是远程分支的名称
#local_branch是本地分支的名称(如果不写则为本地当前分支的名称,如果本地没有分支则会报错,本地初始化仓库时会自动创建默认分支master)
git branch --set-upstream-to=origin/remote_branch local_branch

#例如
git branch --set-upstream-to=origin/master master
git branch --set-upstream-to=origin/master

8. 推送本地分支到上游分支

即推送本地库当前分支到远程库的指定分支。

1
2
3
4
git push 远程库名称 远程库分支

#例如
git push origin master
1
2
3
4
5
#如果已经设置了上游分支,则可以不用加远程库的名称和地址,如下:
git branch --set-upstream-to=origin/master master
git push
#或者可以合并写为如下:
git push --set-upstream origin master #可简写为:git push -u origin master

9. 克隆远程库为本地库

1
2
3
4
git clone 远程库地址 [本地库名称]

#例如:
git clone [email protected]:qcmoke/test.git pro

通过git clone克隆远程仓库到本地,那么git将自动初始化本地仓库,并设置本地分支和对应上游分支,上游分支为远程仓库的默认分支(一般是master分支)。

10. 拉取远程库到本地库

1
2
#拉取远程库到本地库(取回远程库某个分支的更新,再与本地的当前分支进行合并)
git pull

💁‍♂ 说明:

git pull = git fetch 远程库地址 + git merge 远程库名称/远程库分支

git fetch只是下载远程库的分支内容到本地,但没有覆盖掉本地库的分支,需要用git merge来合并远程库的分支到本地库的当前分支。比如git pull命令默认会被解析成如下:

1
2
>git fetch origin master #从origin远程库的master分支下载更新内容到本地 
>git merge FETCH_HEAD #将下载好的远程库分支合并到当前分支中

💁‍♂ 注意:

使用命令git pull拉取分支前,一般是已经通过 git clone 来克隆获取远程库的代码了。如果先前未使用 git clone 的话,需要先添加远程仓库,并设置本地分支的上游分支后才能正常使用 git pull命令来拉取分支 。操作如下:

1
2
3
4
5
>git remote add origin https://host:port/xxx.git
>git branch --set-upstream-to=origin/master master
>git pull
>#设置本地分支的上游分支和拉取两条命令可合并写为如下(注意在git pull中的--set-upstream不能简写为-u):
>git pull --set-upstream origin master

11. 解决推送本地库到远程库冲突的问题

当有两个本地库都连接着同一个远程库,当有一个修改了文件并且推送到了远程库后。另外有个仓库也修改到了同样的文件。如果内容两个推送的修改不一致,那么就造成了冲突。

比如根据之前的例子,本地有pro和project2两个仓库。

(1)修改project2的readme.txt文件的内容为以下:

1
2
I am readme.txt
modify readme.txt by project2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project2 (master)
$ git add readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project2 (master)
$ git commit -m "modify readme.txt by project2"
[master 17938ff] modify readme.txt by project2
1 file changed, 2 insertions(+), 1 deletion(-)

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/project2 (master)
$ git push origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 292 bytes | 146.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:qcmoke/test.git
12f4d1d..17938ff master -> master

(2)在project2修改并提交readme.txt到远程库后,又在本地的pro仓库修改readme.txt,同样提交readme.txt到远程库。

本地的pro修改readme.txt的内容为以下:

1
2
I am readme.txt
modify readme.txt by pro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/pro (master)
$ git add readme.txt

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/pro (master)
$ git commit -m "modify readme.txt by pro"
[master 32cde6b] modify readme.txt by pro
1 file changed, 2 insertions(+), 1 deletion(-)

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/pro (master)
$ git push origin master
To github.com:qcmoke/test.git
! [rejected] master -> master (fetch first)
error: failed to push some refs to '[email protected]:qcmoke/test.git'
hint: Updates were rejected because the remote contains work that you
do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/pro (master)
$

git推送失败,原因是推送的内容与本地仓库project2的推送有冲突,project2推送时,已经更新了一个版本。而pro此时比远程仓库旧了一个版本,当推送时就与远程库的版本一样了,但问题是同样推送到同一个远程仓库版本,而远程仓库的这个版本中的readme.txt已经被project2修改了。git远程仓库无法确定要哪个本地库的修改,所以就冲突了。这要求在被其他本地库修改之前就要更新本地库pro到与远程库相同的版本快照,才能进行修改后推送。

通过以下命令来更新本地库pro到与远程库相同的版本快照:

1
$ git pull origin master

然后再将本地pro的readme.txt的内容修改为以下:

1
2
I am readme.txt
modify readme.txt by pro

然后再推送添加提交到本地仓库,再将本地仓库的最新版本推送到远程仓库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/pro (master)
$ git pull origin master
From github.com:qcmoke/test
* branch master -> FETCH_HEAD
Already up to date.

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/pro (master)
$ git push origin master
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 606 bytes | 202.00 KiB/s, done.
Total 5 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:qcmoke/test.git
17938ff..830af40 master -> master

wen@DESKTOP-BHMU9KJ MINGW64 ~/Desktop/pro (master)
$

十七、团队协同开发

两种方式:

  1. 项目管理者发送请求邀请其他成员加入到项目里,需要管理者通过用户名或者邮箱邀请其他成员,同时其他成员也要同意接受才能加入到项目中,如此其他成员就能git push。
  2. 其他成员先fork项目成为自己的仓库,然后git push到自己的仓库后,发送Pull Requests请求给管理者。管理者同意并merge合并其他成员的修改内容到自己的仓库里。

详细步骤待完成… 😂

十八、问题解决

1. centos7 最小系统精简安装git问题

centos7 最小系统精简安装后,在 git clone 时出现问题提示:fatal: unable to access 'https://xxxxxx.git/': Peer reports incompatible or unsupported protocol version.

解决办法:

1
yum update -y nss curl libcurl
1
2
3
4
vi /etc/profile
#修改:PATH=$PATH:/usr/libexec/git-core
export PATH
source /etc/profile

2. git bash 中文乱码问题解决

乱码如下:

image-20230514142223314

解决办法:

1
git config --global core.quotepath false

core.quotepath 是 Git 的一个配置选项,用于指定在命令输出中是否对文件名进行引号转义。

解决后效果如下:

image-20230514142740416

参考:https://www.php.cn/tool/git/485156.html

十九、Git 常用命令速查表

创建版本库

1
2
git clone                   		#克隆远程版本库
git init #初始化本地版本库

修改和提交

1
2
3
4
5
6
7
8
9
git status                       	#查看状态
git diff #查看变更内容
git add . #跟踪所有改动过的文件
git add #跟踪指定的文件
git mv #文件改名
git rm #删除文件
git rm --cached #停止跟踪文件但不删除
git commit -m "commit messages" #提交所有更新过的文件
git commit --amend #修改最后一次改动

查看提交历史

1
2
3
git log                    	#查看提交历史
git log -p #查看指定文件的提交历史
git blame #以列表方式查看指定文件的提交历史

撤销

1
2
3
4
git reset --hard HEAD      	#撤销工作目录中所有未提交文件的修改内容
git checkout HEAD #撤销指定的未提交文件的修改内容
git revert #撤销指定的提交
git log --before="1 days" #退回到之前1天的版本

分支与标签

1
2
3
4
5
6
7
git branch                   	#显示所有本地分支
git checkout #切换到指定分支和标签
git branch #创建新分支
git branch -d #删除本地分支
git tag #列出所有本地标签
git tag #基于最新提交创建标签
git tag -d #删除标签

合并与衍合

1
2
git merge         #合并指定分支到当前分支
git rebase #衍合指定分支到当前分支

远程操作

1
2
3
4
5
6
7
8
git remote -v                   	#查看远程版本库信息
git remote show #查看指定远程版本库信息
git remote add #添加远程版本库
git fetch #从远程库获取代码
git pull #下载代码及快速合并
git push #上传代码及快速合并
git push #删除远程分支或标签
git push --tags #上传所有标签

更多内容请查看 Git 文档



----------- 本文结束 -----------




如果你觉得我的文章对你有帮助,你可以打赏我哦~
0%