Git学习随笔-从了解到使用
前言
Git作为一个分布式管理控制系统,在日常的开发工作的使用中是十分必要的,之前仅是知道这个工具但是很少使用,通过孟宁老师在高级软件工程课程上的悉心讲解使我对该工具的使用有了更深的理解。本文使用Git完成相关的基本操作,通过结合课上内容和相关资料进行学习从而对Git的基本使用有了初步的了解。由于初次编写博客,本文的结构或部分内容难免冗余,若有错误或不当之处还望批评指正!
1、本地操作
本地版本库可以简单理解为一个目录,且该目录中的所有文件均可被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
1.1、初始化
初始化版本库通过在需要进行Git管理的目录下执行git init
指令进行初始化(本文指令均在D:\git_practice空目录下进行操作):
PS D:\git_practice> git init
Initialized empty Git repository in D:/git_practice/.git/
该输出结果告诉我们已经创建好了一个空的版本库,且生成了一个.git目录,该目录是Git的版本库。
1.2、把文件添加到暂存区
工作区:电脑能看到的目录,现在操作的git_practice就是一个工作区。
暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
工作区、暂存区、版本库之间的关系
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。关系示意图如下所示:

将文件添加到暂存区一般可使用两条指令:git add <file>
和 git add .
分别代表将指定文件和当前目录下所有文件添加到暂存区中。在该目录下新建readme.txt,执行git add readme.md
指令,结果如下:
PS D:\git_practice> git add README.md
PS D:\git_practice>
此时没有任何提示信息输出表明该文档已成功添加到暂存区。
1.3、查看状态
查看版本库的状态可通过指令git status
指令来进行查看:
PS D:\git_practice> git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
该提示信息表明添加到暂存区的文件还未提交到版本库。
1.4、提交到版本库
将暂存区的文件提交到版本库可通过指令git commit -m <message>
来进行提交,-m
后输入的的是对本次提交的说明,可以让自己和相关人员明白此次操作的具体含义:
PS D:\git_practice> git commit -m "add README.md"
[master (root-commit) 241ca01] add README.md
1 file changed, 1 insertion(+)
create mode 100644 README.md
场景1.1:
-
当有新的需求对之前的README.md文件进行了修改,但还未提交到暂存区,此时发现自己修改的内容有错误,此时如何撤销之前的修改呢?
-
当有README.md文件已提交到暂存区,此时有新的需求需要修改该文件,但是修改后发现自己修改的内容有错误,此时如何撤销之前的修改呢?
当然对于该场景可以手动删除出错的内容,但是当文件修改范围较大时,手动删除可能就显得不太方便,除此之外还可以怎么操作呢?
1.5、撤销修改
1.5.1、工作区
撤销工作区的修改可通过指令git checkout -- <file>
或git restore <file>
回到最近一次版本库或暂存区的状态:
PS D:\git_practice> git restore README.md
PS D:\git_practice>
此时可查看该文件可发现已经撤销了之前在工作区的修改。如果对未提交到暂存区的文件进行修改,则撤销修改后与版本库最新内容一致;如果对已提交暂存区的文件进行修改,则撤销修改后内容与暂存区内容一致。
注:Git的restore和下文使用的switch命令是2.23版本发布的新命令,因此旧版本无法使用,所以使用前需查看自己的版本,如果为旧版本,需升级到最新版本才能使用
场景1.2:
- 当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令
git reset HEAD <file>
可以把暂存区的修改回退到工作区,就回到了场景1,第二步按场景1操作。
1.5.2、已提交到暂存区
该操作可通过git reset HEAD <file>
指令来实现,此时之前提交到暂存区的修改会退回到工作区,可再执行撤销工作区修改的指令使其回到未修改前的状态:
PS D:\git_practice> git add README.md
PS D:\git_practice> git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: README.md
PS D:\git_practice> git reset HEAD README.md
Unstaged changes after reset:
M README.md
PS D:\git_practice> 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")
PS D:\git_practice> git restore README.md
PS D:\git_practice> git status
On branch master
nothing to commit, working tree clean
场景1.3:
- 已经提交了不合适的修改到版本库时,想要撤销本次提交,需要进行版本回退。对于版本回退,顾名思义就是将版本库的文件退回到之前的版本,那么我能不能再回到之后的版本呢?
1.6、版本回退
commit id:commit id是一个SHA1计算出来的一个非常大的数字,用40位十六进制数表示,之所以用这么大的数字并非是为了安全性,而是为了数据的完整性。这使得在之后的很长一段时间后回退到之前相应的commit id时一定还是当初的状态。
首先必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是提交日志的最新记录的commit id.,上一个版本就是HEAD,上上一个版本就是HEAD,当然往上100个版本写100个比较容易数不过来,所以写成HEAD~100。
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD指向进行了更改。然后顺便把工作区的文件更新了。所以你让HEAD指向哪个版本号,你就把当前版本定位在哪。
版本回退可通过指令git reset --hard HEAD^^/HEAD~100/commit-id/commit-id的头几个字符
,仅回退到前几个版本仅需通过HEAD来进行操作,但是如果不记得该版本到底是哪一个版本该怎么办呢?此时可以通过指令git log
来查看当前HEAD之前的提交记录(为了方便查看该指令的效果,对README.md文件进行两次修改,并依次进行add和commit):
PS D:\git_practice> git log
commit 5127668582f75081d431744d2262d861de494a02 (HEAD -> master)
Author: username <xxxxxx@gmail.com>
Date: Fri Oct 16 11:27:42 2020 +0800
modify README.md for the second time
commit 2edbfd1b22b62e8bff2be3c08a8bceebdc10052f
Author: username <xxxxxx@gmail.com>
Date: Fri Oct 16 11:26:42 2020 +0800
modify README.md for the first time
commit 241ca019cd1732976c98c3b2d4ae5668d645a033
Author: username <xxxxxx@gmail.com>
Date: Thu Oct 15 22:11:11 2020 +0800
add README.md
如果觉得输出内容太过繁杂,可对指令git log --pretty=oneline
使输出内容简化为一行内容,此时仅会输出commit id和提交说明。
PS D:\git_practice> git log --pretty=oneline
5127668582f75081d431744d2262d861de494a02 (HEAD -> master) modify README.md for the second time
2edbfd1b22b62e8bff2be3c08a8bceebdc10052f modify README.md for the first time
241ca019cd1732976c98c3b2d4ae5668d645a033 add README.md
此时每行前显示的40位十六进制数就是commit id,我们可以通过git reset --hard <commit id前几位>
,此时使用commit id不能太短,可能会找到多个commit id,此时会提示unknown revision or path not in the working tree.
的错误,本例中写了5位,这样不容易重复:
PS D:\git_practice> git reset --hard 2edbf
HEAD is now at 2edbfd1 modify README.md for the first time
PS D:\git_practice> git log --pretty=oneline
2edbfd1b22b62e8bff2be3c08a8bceebdc10052f (HEAD -> master) modify README.md for the first time
241ca019cd1732976c98c3b2d4ae5668d645a033 add README.md
此时通过提示信息可以了解已经回退到了上一次的提交,查看HEAD之前的提交记录发现之前最新的一次提交已经不再记录中了。
场景1.4:
通过场景1.3我们已经回退到上一个版本了,但是我现在忽然觉得我最新的修改是有意义的,那我又该如何回到“未来”的版本呢?最简单的方法就是查看上文中最新提交的commit id,通过指令git reset --hard <commit id的前几位>
回到“未来”,但是如果我们并不知道最新的commit id,那又该如何恢复最新的修改呢?
1.7、回到“未来”
通过指令git reflog
可以查看历史命令,从而可以得到最新提交的commit id,此时便可通过指令git reset --hard <commit id的前几位>
来重新回到“未来”:
PS D:\git_practice> git reflog
2edbfd1 (HEAD -> master) HEAD@{0}: reset: moving to 2edbf
5127668 HEAD@{1}: commit: modify README.md for the second time
2edbfd1 (HEAD -> master) HEAD@{2}: commit: modify README.md for the first time
PS D:\git_practice> git reset --hard 51276
HEAD is now at 5127668 modify README.md for the second time
通过输出内容可以看出此时已经成功回到了“未来”,工作区的内容也已经自动作出了相应的修改。
2、远程操作
以上操作均在本地进行的操作,同时你也可以将本地的Git仓库上传到GitHub上,这样能方便地将本地的仓库在远程库中进行备份或者用于其它用途。那么如何才能将本地的版本库上传到GitHub上呢?
第一步,由于本地的Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以必须要让github仓库认证你SSH key。可以通过查看C盘->用户->(你的用户名)->.ssh
路径下是否有id_rsa、id_rsa.pub文件,若存在则直接执行第二步的操作,否则在Git Bash中输入以下指令:
$ ssh-keygen -t rsa -C "你的邮箱地址<youremail@example.com>"
执行该指令后,一般情况下无需设置密码,一直使用默认状态回车即可,操作结束后会生成相应的文件。
第二步,登录自己的GitHub账号,点击右上角图像,选择Setting选项,勾选SSH and GPG keys界面如下图所示:

默认SSH keys里并没有内容,此时需要通过点击“New SSH key”按钮来新增一个SSH key,新增界面如下:

填入任意Title,将id_rsa.pub文件的内容粘贴到Key文本框中,点击Add SSH key完成添加。完成以上配置后,你就可以在Github上新建一个远程库与自己的本地库相关联了。
第三步,在GitHub上新建一个仓库,点击右上角+号,选择New repository选项创建一个新的仓库:

在该界面输入与本地版本库管理的目录相同的名字方便辨识,然后点击“Create repository”按钮即可创建一个空的仓库,创建成功界面如下所示:

此时,可以界面上的复制按钮复制该远程库的ssh的url,以便下述操作。
2.1、关联远程库
通过上图提示的命令可以知道通过指令git remote add origin git@server-name:path/repo-name.git
将本地仓库与该远程库关联起来:
PS D:\git_practice> git remote add origin git@github.com:<server-name>/git_practice.git
PS D:\git_practice>
2.2、推送远程库
通过指令git push -u orign master
将当前master分支的内容推送到远程库的master分支中,在第一次推送到master分支时加上了-u
参数,这不仅会将本地的master分支内容推送到远程新的分支,同时还会将本地的master分支与远程的分支关联起来,这样在以后推送或拉取时可以简化相关指令:
PS D:\git_practice> git push -u 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% (9/9), 761 bytes | 380.00 KiB/s, done.
Total 9 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:<server-name>/git_practice.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
通过指令git remote
可查看已经存在的远程分支,通过在指令后加入-v(此为 --verbose 的简写,取首字母)
参数可列出详细信息,在每一个名字后面列出其远程url:
PS D:\git_practice> git remote
origin
PS D:\git_practice> git remote -v
origin git@<server-name:path>/git_practice.git (fetch)
origin git@<server-name:path>/git_practice.git (push)
PS D:\git_practice> git remote --verbose
origin git@<server-name:path>/git_practice.git (fetch)
origin git@<server-name:path>/git_practice.git (push)
2.3、克隆远程库
当你出门在外时更换了一台电脑,但此时想查看之前已经提交到远程库的文件进行更改,这时可以将将远程库克隆到本地进行修改,这个操作可通过指令git clone git@server-name:path/repo-name.git
完成(此时将远程库克隆到空的文件夹git_practice1下):
PS D:\git_practice1> git clone git@<server-name:path>/git_practice.git
Cloning into 'git_practice'...
Warning: Permanently added the RSA host key for IP address '192.30.255.112' to the list of known hosts.
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 9 (delta 0), reused 9 (delta 0), pack-reused 0
Receiving objects: 100% (9/9), done.
此时该文件夹的内容就和你之前电脑中的仓库内容相同了,此时可以对仓库里的文件进行更改了,更改完成后先提交到本地版本库再通过上述指令git push orgin master
将修改的内容推送到远程库的master分支上(注:对于不同的电脑需要在Github中新增该电脑的SSH key,由于演示均在同一台电脑,故无需该操作):
PS D:\git_practice1> cd git_practice
PS D:\git_practice1\git_practice> git add README.md
PS D:\git_practice1\git_practice> git commit -m "modify README.md in another computer"
[master 8d75c5b] modify README.md in another computer
1 file changed, 2 insertions(+), 1 deletion(-)
PS D:\git_practice1\git_practice> git push origin master
Warning: Permanently added the RSA host key for IP address '192.30.255.113' to the list of known hosts.
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), 324 bytes | 324.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:chenghao9896/git_practice.git
5127668..8d75c5b master -> master
2.4、本地远程同步
通过上述操作,我们已经对对远程库的内容进行了修改,当我们使用之前的电脑时如何使本地库的内容与远程库的内容相同呢?可以通过指令git pull origin master
将指定远程分支同步到当前本地分支,此时在git_practice目录下执行该条指令:
PS D:\git_practice> git pull origin master
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
Unpacking objects: 100% (3/3), 304 bytes | 1024 bytes/s, done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
From github.com:chenghao9896/git_practice
* branch master -> FETCH_HEAD
5127668..8d75c5b master -> origin/master
Updating 5127668..8d75c5b
Fast-forward
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
通过git pull拉取远程仓库里的提交项到本地仓库并合并到当前分支,相当于将origin/master中的提交项fetch到本地仓库并merge到本地master分支,由于该场景演示均为单人操作,并没有冲突产生。
注:实际的应用场景中,拉取过程中有可能会有冲突的情况,无法完成merge合并动作,这时需要对冲突的代码进行修改并提交到本地仓库,即利用git add、git commit -m等本地版本库的操作。
3、多人协作
在多人协作开发的过程中,分支显得尤为重要。首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能该分支上进行修改操作。此时需要新建一个dev分支,当在该分支上完成了所有的工作再合并到master分支上,然后发布新版本。多人协作也是一样,每个人都新建一个分支,同时也只在自己的分支上工作,完成阶段性的工作后可以往dev分支上合并,示意图图下:

每一个开发者都采用的工作流程大致可以简述如下:
- 克隆或同步最新的代码到本地存储库;
- 为自己的工作创建一个分支,该分支应该只负责单一功能模块或代码模块的版本控制;
- 在该分支上完成某单一功能模块或代码模块的开发工作;
- 最后,将该分支合并到主分支。
3.1、创建分支
此处在之前克隆到本地的存储库中创建一个dev分支,创建并切换分支可以通过指令git checkout -b <分支名>
或指令git switch -c <分支名>
来完成,为了与上文撤销修改指令相区别,以下均用git switch -c dev
来创建和切换分支,通过指令git branch
可以查看当前所在分支:
PS D:\git_practice1\git_practice> git switch -c dev
Switched to a new branch 'dev'
PS D:\git_practice1\git_practice> git branch
* dev
master
此时已成功创建一个新的dev分支,且该分支上的文件和master分支上的文件内容相同,在dev分支上对文件进行修改并不会对master分支产生任何影响。
3.2、合并分支
现在在该分支上对文件进行相关的修改,修改完成后需要合并到主分支,该操作需要先通过指令git checkout <主分支名>
或指令git switch <主分支名>
切回到主分支,将远程origin/master同步最新到本地存储库,再合并dev分支的内容到主分支,然后再将其推送到远程origin/master之后即完成了一项开发工作。指令git merge --no-ff <需要合并的分支名>
将主分支和创建的新分支进行合并:
PS D:\git_practice1\git_practice> git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
PS D:\git_practice1\git_practice> git pull origin master
From github.com:<server-name>/git_practice
* branch master -> FETCH_HEAD
Already up to date.
PS D:\git_practice1\git_practice> git merge --no-ff dev
Merge made by the 'recursive' strategy.
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
PS D:\git_practice1\git_practice> git push origin master
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 4 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 631 bytes | 631.00 KiB/s, done.
Total 7 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), done.
To github.com:<server-name>/git_practice.git
8d75c5b..7ffeede master -> master
此时在dev分支上的修改就已经合并到主分支上了。通常情况下,合并分支时,Git会默认用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息,从而就看不出来曾经做过合并。合并分支的另一种模式可以通过加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,其网络图如下,可以清晰的看到合并的路径:

3.3、删除分支
当已经在该分支上完成某单一功能模块或代码模块的开发工作而且也合并到了主分支上,此时若没有新的需求,可以通过指令git branch -d <需要删除的分支名>
来删除该分支:
PS D:\git_practice1\git_practice> git branch
dev
* master
PS D:\git_practice1\git_practice> git branch -d dev
Deleted branch dev (was b1a1797).
PS D:\git_practice1\git_practice> git branch
* master
Git分支的相关知识:
刚开始使用Git时,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支,即master分支,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点,示意图如下所示:

每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上,示意图如下所示:

Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化。
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:

假如我们在dev上的工作完成了,就可以把dev合并到master上。最简单的方法,就是直接把master指向dev的当前提交,就完成了合并,即默认的Fast forward模式。示意图如下所示:

3.4、整理提交记录
当我们在本地仓库中提交了多次,在我们把本地提交push到公共仓库中之前,为了让提交记录更简洁明了,我们希望把对同一个文件的三个提交记录合并为一个完整的提交,这样可以使得提交记录更加整洁,同时也方便以后自己或他人对提交记录进行查看。此处假设完成了A(A由A1、A2、A3组成),分别提交了三次,此处可以通过指令git rebase -i HEAD~3
来整理前三次提交:

此时进入了一个文本编辑器,用法大致与vim相同。按i键进入插入编辑模式来修改对commit指令的操作;按ESC键退出编辑模式回到一般命令模式(Normal Mode),这时按:键进入底线命令模式,输入:wq保存退出、输入:q退出、输入:q!强制退出。由于想把前三次提交合并,因此对commit内容编辑如下:
pick e8fa5b7 add A1 feature
s 5470b3b add A2 feature
s 8932537 add A3 feature
保存退出后进入到日志修改界面(此处未修改):

此时保存后即可完成commit的合并操作了,此处可通过指令git log
指令清楚的观察到合并的结果:
PS D:\git_practice1\git_practice> git log
commit b1c72af8f2ff7b0af1a2cd15d1ccdf36a8c85ac5 (HEAD -> master)
Author: <username> <your eamil>
Date: Mon Oct 19 21:25:23 2020 +0800
add A1 feature
add A2 feature
add A3 feature
注:Rebase指令的其它相关用法可以参考rebase 用法小结。