Git学习记录(一)

时间:2021-11-05 20:48:38

本篇文章介绍Git的本地使用

Git是什么?

Git是世界上最先进的分布式版本控制系统。

那么什么是版本控制系统?

我们来举个例子,假设我创建了一个项目Project.1,里面写了一个README.txt文档、一个code1.cs和一个code2.cs,第二天我突然想改进一下版本,但是我们不能直接在这个项目里改,因为如果直接在这里面改,我们要是想要退回去的话就会很麻烦,因为我们可能记不住上一个版本里面的代码是什么样子的,而且改代码这个东西经常是改一个地方就要将许多地方一同改掉。所以,我就想了一个办法,就是将这个项目拷贝一份,然后将他重命名为Project.2。这就是我们最朴素的一个版本管理的方法。

但是随着代码量的增加,这种方法无疑是不太靠谱的,如果你的程序是多人合作开发的,那问题会更大,因为如果一联网,大家的版本互相覆盖,到底哪个版本是谁的,该用哪个版本就会出问题了。

Linus在开发Linux系统的时候就遇到了这个问题,而为了解决这个问题,Linus最终花费两周的时间用C语言写出了Git。

分布式又是什么?

先说说集中式版本控制系统,集中式版本控制系统的版本库是集中存放在*服务器的,但是我们干活的时候都是用的自己的电脑,所以要先从*服务器中取得最新的版本,然后开始干活,再把自己干好的活推送到*服务器。这个集中式版本控制系统最大的毛病就是联网才能干活,单纯是在局域网内的话还好,但是如果是在互联网上,遇到网速慢的时候可能提交和下载文件就要浪费很多时间。

那么分布式版本控制系统呢?首先,分布式版本控制系统根本没有*服务器,每个人的电脑上都是一个完整的版本库,这样工作的时候就不需要联网了,因为版本库就在自己的电脑上。那么该如何多人协作呢?比方说,你在你的电脑上修改了文件A,你的同事也在他的电脑上修改了文件A,这时你俩只需要把各自修改的部分推送给对方,就可以互相看到对方的修改了。

和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人的电脑中都有一个完整的版本库,某个人的电脑坏了不要紧,只需要去别人哪里拷贝一下就可以了。但是集中式版本控制系统的*服务器要是出问题了,所有人就都没法干活了。

在实际使用分布式版本控制系统时,其实很少在两人之间的电脑上互相推送版本库的修改,因为你们不在同一个局域网内的话电脑是没法互相访问的。所以,分布式版本控制系统也有一台充当*服务器的电脑,不过这个服务器仅仅是用来方便大家交换修改的,就算挂掉了也没啥大事。

Git是如何进行版本控制的?

其他的版本控制系统例如SVN,他们是将每一次版本的变动记录下来,这样就像写小说一样,每次就只增加一个章节,这种存储方式叫做增量文件系统。而Git是将每个版本独立保存起来,也就是说如果某个版本中文件发生了变动,Git就会将整个文件复制并保存起来,这样就像是写小说的,每次写新章节就会将以前的章节再写一遍。这种设计看似会消耗更多的空间,但在后续的分支管理上带来了很多的好处和便利。

安装Git

  1. 下载Git --> https://git-scm.com/download/win

  2. 安装,一路Next即可

  3. 配置用户信息,安装完成后我们要在Git Bash中输入以下两行命令

    git config --global user.name "your name"
    
    git config --global user.email "email@example.com"
  4. 可以使用git config --list查看配置信息

Git也逃不开增删改查

众所周知,一切项目的基础是增删改查,Git也是如此,接下来我们来看看Git中的增删改查。

首先我们先在本地使用Git。

我们在本地创建的仓库是由三个区域组成,这是Git的核心架构。即:工作区域、暂存区域和Git仓库(版本库)。

创建版本库

首先,选择一个合适的地方,创建一个空目录。

$ mkdir learngit
$ cd learngit
$ pwd

pwd命令用于显示当前目录的路径

第二步,通过git init命令将这个目录变成Git可以管理的仓库

$ git init

这样Git瞬间就将仓库建好了,目录下会多出一个.git隐藏目录,这个目录就是Git的版本库,使用ls -ah就可以看见。

将文件添加到版本库

手动添加至刚刚创建的目录下即可,但是要注意千万不要使用windows自带的记事本编辑任何文本文件,因为它会给所有UTF-8编码的文件开头添加一个0xefbbf的字符。

我们我们进入刚刚创建的learngit目录,在做项目的时候我们通常都会有一个README.md来介绍自己的项目,所以在这里我们先编写一个README.md文件,内容如下

This is a project to learn git

保存后这个文件就存放在了Git的工作区域中,工作区域就是平时我们存放项目代码的地方。

然后,我们执行git add命令

$ git add README.md

执行完该命令后,是没有任何显示的,这时我们就将文件放入Git的暂存区了,暂存区实际上只是一个文件,保存我们即将提交的文件列表信息。

接下来使用git commit将文件提交至仓库

$ git commit -m "wrote a readme file"

在这个命令中,-m后面输入的是本次提交的说明,可以输入任何内容,当然最好是有意义的,这样你可以从历史记录中方便的找到改动记录。

git commit执行成功后会告诉你1 file changed一个文件被改动(README.md)1 insertions(+)插入了一行内容(README.md中有一行内容)。

我们除了可以使用一条add跟一条commit来提交一个文件,还可以用一条commit提交很多个文件,比如:

$ git add code1.cs
$ git add code2.cs code3.cs
$ git commit -m "add 3 cs files"

至此,我们完成了一次完整的Git工作流程,就是先在工作目录中添加、修改文件,将需要进行版本管理的文件放入暂存区,将暂存区的文件提交到Git仓库。

查看工作状态

我们应该都清楚,在平时写代码做项目的时候必然会创建许多文件,文件一多,我们就有可能记不住哪些文件添加到暂存区了,哪些文件提交了。这时我们需要用到另一个命令来查看文件的状态git status。首先,我们先来执行一遍

$ git status
On branch master
nothing to commit, working tree clean

Git告诉我们当前没有需要提交的修改,工作目录是干净的,这就说明commit提交后的文件用git status是不会显示的。

接下来我们在工作目录中添加一个新的文件LICENSE,然后再次执行git status

$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
LICENSE nothing added to commit but untracked files present (use "git add" to track)

这时我们发现LICENSE文件的状态是Untracked,这就表示这个文件没有添加到暂存区,不参与版本控制。在输出中有一行被圆括号包裹着,这一行是Git给我们的操作建议,我们不妨按照这个建议走下去,执行完git add后再次执行git status

$ git add LICENSE
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: LICENSE

这时我们会发现,Git的建议操作变成了use "git restore --staged <file>..." to unstage这个建议的意思是使用这个命令来让文件离开暂存区。

我们执行这个命令后再次查看状态

$ git restore --staged LICENSE
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
LICENSE nothing added to commit but untracked files present (use "git add" to track)

这样LICENSE的状态就变回Untracked了。

接下来我们对README.md文件进行修改,改成如下内容

This is a big project to learn git
I love git

然后再次运行git status命令查看结果

$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md no changes added to commit (use "git add" and/or "git commit -a")

我们会发现,在输出中有modified: README.md以及no changes added to commit 这个意思就是README.md这个文件被修改过了,但还没有准备提交的修改。modified就是README.md当前的状态。

这个命令虽然告诉我们README.md文件被修改了,但有时候我们可能想看看具体修改了什么内容,这时候我们就需要git diff命令

$ git diff README.md
diff --git a/README.md b/README.md
index ec489ee..3e420a2 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
-This is a project to learn git
\ No newline at end of file
+This is a big project to learn git
+I love git
\ No newline at end of file

diff就是difference的意思,在上面的输出中我们可以看到第一行增加了一个big单词,以及新增了一行I love git。在知道了具体修改内容后,我们就可以放心的完成addcommit命令了。我们拐回头去看看上次执行git status时Git给的建议use "git add <file>..." to update what will be committeduse "git restore <file>..." to discard changes in working directory,第一个我们已经十分熟悉了,第二个建议是我们可以使用git restore来放弃更改,我们来执行一下这个命令试试。

$ git restore README.md

没有任何输出,但是这时我们再查看README.md文件会发现我们的更改都消失了,所以在使用这个命令的时候一定要慎重。

版本回退

下面我们再修改一次README.md文件

This is a big project to learn git
I love git
这是一个学习Git的项目

然后提交

$ git add README.md
$ git commit -m "add chinese translation"
[master 310d469] add chinese translation
1 file changed, 2 insertions(+), 1 deletion(-)

像我们这样不断对文件进行修改,然后不断提交修改到版本库中,就像我们玩游戏的时候每打到一定程度就存档,这样如果在后面的某一个地方失败了,我们还可以读档从最近的进度开始而不用从头开始。前面我们所做的commit操作就像是存档操作,读档操作我们可以使用git loggit reset命令完成。

首先我们查看一下我们一共有多少“存档”(上面操作的顺序我改了改,所以这里我的版本顺序有些不对

$ git log
commit 310d469f13a7f6d4add1edf323ad8ef194e7a70d (HEAD -> master)
Author: Tuzilow <1176281967@qq.com>
Date: Sun Nov 17 10:02:19 2019 +0800 add chinese translation commit 3e6ef04eaab69eaf4ece551c5cd76f65b9ab31b2
Author: Tuzilow <1176281967@qq.com>
Date: Sat Nov 16 22:23:48 2019 +0800 add a LICENSE file commit 4572a22c5f04abe587fa24a41da651cea02a2b55
Author: Tuzilow <1176281967@qq.com>
Date: Sat Nov 16 21:47:47 2019 +0800 add a newline commit 01c756183d30c82fd54e600e11a36733cf794fd6
Author: Tuzilow <1176281967@qq.com>
Date: Sat Nov 16 21:38:13 2019 +0800 change readme commit 45b6484663bf370880d470427a1e388a006452e0
Author: Tuzilow <1176281967@qq.com>
Date: Sat Nov 16 21:36:44 2019 +0800 add a readme file

git log命令会显示从最近到最远的提交日志,如果觉得输出的信息太多可以尝试加上--pretty=oneline参数

$ git log --pretty=oneline
310d469f13a7f6d4add1edf323ad8ef194e7a70d (HEAD -> master) add chinese translation
3e6ef04eaab69eaf4ece551c5cd76f65b9ab31b2 add a LICENSE file
4572a22c5f04abe587fa24a41da651cea02a2b55 add a newline
01c756183d30c82fd54e600e11a36733cf794fd6 change readme
45b6484663bf370880d470427a1e388a006452e0 add a readme file

这前面的一大串像乱码一样的东西是commit id,就是版本号,为了防止多人协作的时候产生冲突,Git没有采用简单的1、2、3……作为版本号。

接下来我们读取存档,我们讲README.md退回到上一个版本

$ git reset --hard HEAD^
HEAD is now at 3e6ef04 add a LICENSE file $ cat README.md
This is a big project to learn git
I love git

在Git中HEAD表示当前版本,HEAD^表示上一个版本,上上一个版本就是HEAD^^,如果是网上100个版本就没必要写100个^,可以写成HEAD~100。我们可以看到README.md文件被成功还原了,这时我们再查看版本库的状态

$ git log --pretty=oneline
3e6ef04eaab69eaf4ece551c5cd76f65b9ab31b2 (HEAD -> master) add a LICENSE file
4572a22c5f04abe587fa24a41da651cea02a2b55 add a newline
01c756183d30c82fd54e600e11a36733cf794fd6 change readme
45b6484663bf370880d470427a1e388a006452e0 add a readme file

会发现最新的add chinese translation版本已经找不到了,这个时候如果我们想要重新回到这个版本就需要用到这个版本的commit id

$ git reset --hard 310d46
HEAD is now at 310d469 add chinese translation $ cat README.md
This is a big project to learn git
I love git
这是一个学习Git的项目

版本号没必要写全,只需要写前几位Git就会自动去找。Git的版本回退是非常快的,因为在Git内部有一个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD指针指向你要回退的版本,然后将该版本回退到暂存区并将暂存区还原到工作目录,让你看到回退后的文件。

现在,如果我们回退到某个版本后想要回去,但是忘记了commit id,命令行还清理了找不到最新版本的commit id,这时我们可以使用git reflog,这个命令会记录我们每一次对版本的操作

$ git reflog
310d469 (HEAD -> master) HEAD@{0}: reset: moving to 310d46
3e6ef04 HEAD@{1}: reset: moving to HEAD^
310d469 (HEAD -> master) HEAD@{2}: commit: add chinese translation
3e6ef04 HEAD@{3}: commit: add a LICENSE file
4572a22 HEAD@{4}: commit: add a newline
01c7561 HEAD@{5}: commit: change readme
45b6484 HEAD@{6}: commit (initial): add a readme file

管理修改

下面我们继续修改README.md文件

This is a big project to learn git
I love git
这是一个学习Git的项目
C# is a good programming language

然后添加

$ git add README.md
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: README.md

然后再次修改README.md

This is a big project to learn git
I love git
这是一个学习Git的项目
C# is a good programming language
Java is also a good programming language

提交,然后查看状态

$ git commit -m "C# is good"
[master 61c5ed5] C# is good
1 file changed, 2 insertions(+), 1 deletion(-) $ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md no changes added to commit (use "git add" and/or "git commit -a")

这时我们会发现,README.md仍然显示为修改后未提交。

我们使用git diff HEAD -- README.md命令查看一下当前仓库中的文件和工作区的文件有什么区别(中文可能会有乱码)

$ git diff HEAD -- README.md
diff --git a/README.md b/README.md
index a8963da..08874e0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
This is a big project to learn git
I love git
<E8><BF><99><E6><98><AF><E4><B8><80><E4><B8><AA><E5><AD><A6><E4><B9><A0>Git<E7><9A><84><E9><A1><B9><E7>AE>
-C# is a good programming language
\ No newline at end of file
+C# is a good programming language
+Java is also a good programming language
\ No newline at end of file

我们回顾一下刚才的操作:

第一次修改 -> git add -> 第二次修改 -> git commit

这时我们会发现,第二次的修改并没有被提交成功,因为git commit只会提交被git add添加至暂存区的修改,并非是文件。如果我们想要提交第二次修改,只需要再执行一次git addgit commit

有些时候我们可能会遇到commit后发现自己提交的说明不够详细,想要更改说明,这时就要用到git commit --amend

$ git commit --amend
java is good # Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Sun Nov 17 12:12:57 2019 +0800
#
# On branch master
# Changes to be committed:
# modified: README.md
#
~
~

这时会显示一个这样的界面,第一行就是我们添加的说明,按下“I”键,就可以更改。更改完成后,按下“ESC”,然后输入“:” + “wq” + 回车,就可以完成修改,如果不想修改使用“:” + “q!” + 回车即可。或者我们可以直接使用git commit --amend -m "新的说明"即可完成更改。

删除文件

接下来我们在文件管理器中删掉LICENSE文件,或者使用rm命令也可以

rm LICENSE

这个时候,工作区的文件不在了,但是版本库中还存在这个文件,这时我们查看状态:

$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: LICENSE no changes added to commit (use "git add" and/or "git commit -a")

git status会告诉我们这个文件被删除了,这时我们有两个选择,一是从版本库中删除这个文件,使用git rm并执行git commit

$ git rm LICENSE
rm 'LICENSE'
$ git commit -m "remove LICENSE"
[master d78ec29] remove LICENSE
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 LICENSE

二是使用git restore恢复该文件

$ git restore LICENSE
$ ls
LICENSE README.md

分支管理

分支可以说是Git中最强大的功能,接下来我们就来学习一下Git中的分支。

分支到底有啥用呢?假设我们有一个项目已经上线了,但是要开发一个新的功能,这个功能要写好几天才能完成,如果我们在代码没有写完就提交的话很有可能会影响项目的正常运行,但是如果我们一次性写完再提交,又面临着丢失进度的巨大风险。

如果我们使用分支的话,就可以避免这个问题。我们可以创建一个自己的分支,别人还在原来的分支上工作,而我们在自己的分支上工作可以随意提交也不会影响整个项目,直到功能开发完毕后一次性将分支合并到原来的分支上,这样安全又不会影响别人工作。

事实上,其他的版本控制系统都支持分支管理,但是这些版本控制系统的分支管理的速度非常慢,而在Git中,无论是创建、切换还是删除分支,无论在你的版本库中有多少文件,它都能在1分钟内完成(因为Git每一个节点都是一个完整的项目副本)。

创建、切换、合并与删除分支

首先我们来创建一个分支,并且切换到这个分支

$ git branch dev
$ git checkout dev
Switched to branch 'dev'

这两行命令可以用git checkout -b dev来代替。接下来我们用git branch来查看分支。

$ git branch
* dev
master

git branch命令会列出所有分支,并且会在当前分支前加一个*,在这个分支列表中我们会发现,这里除了我们刚刚创建的dev分支,还有一个master分支,这个分支是Git为我们创建的一个默认分支。

接下来我们在当前分支上提交修改。修改README.md文件

This is a big project to learn git
I love git
这是一个学习Git的项目
C# is a good programming language
Java is also a good programming language
Create a new branch

然后提交

$ git add README.md
$ git commit -m "new branch test"
[dev 571df6d] new branch test
1 file changed, 2 insertions(+), 1 deletion(-)

接下来我们切换回master分支,然后查看文件

$ git checkout master
Switched to branch 'master' $ cat README.md
This is a big project to learn git
I love git
这是一个学习Git的项目
C# is a good programming language
Java is also a good programming language

这时我们会发现,刚才添加的内容不见了,这是因为刚才添加的内容在dev分支上,master分支还没有改变。

接下来我们把dev分支的工作成果合并到master分支上

$ git merge dev
Updating d78ec29..571df6d
Fast-forward
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

然后再查看README.md

$ cat README.md
This is a big project to learn git
I love git
这是一个学习Git的项目
C# is a good programming language
Java is also a good programming language
Create a new branch

这样master分支就和dev分支完全一样了,在合并分支的输出中有一句Fast-forward,这就表示这次分支模式是“快进模式”,也就是直接将master指向dev的当前提交,所以合并速度非常快。

合并完成后,我们就可以删除分支了

$ git branch -d dev
Deleted branch dev (was 571df6d). $ git branch
* master

现在就只剩下master分支了

解决合并冲突

有时候我们合并分支并不会这么轻松,分支间经常会出现各种各样的冲突。接下来我们就要解决这些冲突。

先准备一个新的分支,并在这个分支上提交新的修改

$ git checkout -b feature
Switched to a new branch 'feature'
This is a big project to learn git
I love git
这是一个学习Git的项目
C# is a good programming language
Java is also a good programming language
Create a new branch
Create a new feature branch
$ git add README.md
$ git commit -m "new feature branch"
[feature e5c49d4] new feature branch
1 file changed, 2 insertions(+), 1 deletion(-)

然后切换回master分支,并将修改文件

$ git checkout master
Switched to branch 'master'
This is a big project to learn git
I love git
这是一个学习Git的项目
C# is a good programming language
Java is also a good programming language
Create a new branch
Create a new feature branch is easy

提交

$ git add README.md
$ git commit -m "is easy"
[master f730807] is easy
1 file changed, 2 insertions(+), 1 deletion(-)

现在,两个分支都有了各自的新提交,我们可以使用git log --decorate --graph --oneline --all将提交历史和所属分支以图形化显示出来

$ git log --decorate --graph --oneline --all
* f730807 (HEAD -> master) is easy
| * e5c49d4 (feature) new feature branch
|/
* 571df6d new branch test
* d78ec29 remove LICENSE
* 959d046 java is good
* 61c5ed5 C# is good
* 310d469 add chinese translation
* 3e6ef04 add a LICENSE file
* 4572a22 add a newline
* 01c7561 change readme
* 45b6484 add a readme file

在这种情况下,两个分支是无法快速合并的,只能试图把各自的修改合并起来,但这种合并就可能会出现冲突。

$ git merge feature
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

这个时候Git就告诉我们README.md文件出现了冲突,必须手动解决冲突后再提交。

git status也会告诉我们冲突文件

$ git status
On branch master
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.md no changes added to commit (use "git add" and/or "git commit -a")

我们可以直接查看README.md的内容

This is a big project to learn git
I love git
这是一个学习Git的项目
C# is a good programming language
Java is also a good programming language
Create a new branch
<<<<<<< HEAD
Create a new feature branch is easy
=======
Create a new feature branch
>>>>>>> feature

Git用<<<<<<<=======>>>>>>>标记出不同分支的内容,我们修改后保存再次提交

Create a new feature branch is simple
$ git add README.md
$ git commit -m "is simple"
[master 1ecaa98] is simple

然后我们再次查看分支情况

$ git log --decorate --graph --oneline --all
* 1ecaa98 (HEAD -> master) is simple
|\
| * e5c49d4 (feature) new feature branch
* | f730807 is easy
|/
* 571df6d new branch test
* d78ec29 remove LICENSE
* 959d046 java is good
* 61c5ed5 C# is good
* 310d469 add chinese translation
* 3e6ef04 add a LICENSE file
* 4572a22 add a newline
* 01c7561 change readme
* 45b6484 add a readme file

这时分支就合并成功了,最后删除feature分支

$ git branch -d feature
Deleted branch feature (was e5c49d4).

分支管理策略

在实际开发中,我们应该按照几个基本原则来进行分支管理:

首先,master分支应该是非常稳定的,仅用来发布新的版本,平时不能在上面干活。dev分支(develop)是我们平时干活的地方,开发到一定程度的时候把dev分支合并到master分支上,再master分支上发布1.0版本。一般来说我们每个人都有自己的dev分支,时不时的往云端的dev分支合并就行了。

hotfix通常用来修复bug,release分支通常用来测试要合并到master分支上的版本,feature分支用来开发新功能

Git学习记录(一)

参考文章