0%

git 学习笔记

这个文件是学习git操作的主要改动目标,我会在这里记录我的学习笔记,顺便用这个文件的改动过程来记录git的各项操作。

以下内容是我在廖雪峰官网上学习git教程 的相关笔记,部分内容也来自该网站,此处只做记录。


每次改动后,都要:

1
2
git add
git commit

版本回退

Git中,一次commit就是一次快照,会分配一个唯一的commit id

要回退之前的版本,用到的命令是git reset

在Git中,用HEAD表示当前版本,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100

1
2
git reset --hard HEAD^	# 回退上一个版本
git reset --hard 1094a # 回退(指向)到commit id为1094a...的版本

git log:Git中查看历史提交记录(逻辑上的时间线,回退之后就看不到 ’未来‘ 的版本记录了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
commit 591c501066e6027a4226d178e562b2a95bf20f47 (HEAD -> master)
Author: guiu <hcl01125@gmail.com>
Date: Fri Feb 21 01:16:27 2020 +0800

修改markdown格式错误,以后改动就用typora来做。

commit 7a8d5987913278682362982e1adff747de0f0ecf
Author: guiu <hcl01125@gmail.com>
Date: Fri Feb 21 01:12:11 2020 +0800

创建一个学习笔记文件,一边学习一边写笔记

commit 950da97867e275f166a5f37cf9484d4b45e5b986
Author: guiu <hcl01125@gmail.com>
Date: Fri Feb 21 01:06:02 2020 +0800

the first change

commit 67979fd8ea9695d9a3972e9299466e8395faf3b5
Author: guiu <hcl01125@gmail.com>
Date: Fri Feb 21 01:01:47 2020 +0800

添加主要改动对象---demo.txt

git reflog:Git中查看历史命令(实际操作的时间线,回退的记录也会在里面)

1
2
3
4
5
6
7
8
9
10
91c501 (HEAD -> master) HEAD@{0}: reset: moving to HEAD^
68cede9 HEAD@{1}: commit: 这一次模拟误操作切提交了的情况
591c501 (HEAD -> master) HEAD@{2}: commit: 修改markdown格式错误,以后改动就用typora来做。
7a8d598 HEAD@{3}: commit: 创建一个学习笔记文件,一边学习一边写笔记
950da97 HEAD@{4}: commit: the first change
67979fd HEAD@{5}: commit: 添加主要改动对象---demo.txt
e5c14e1 HEAD@{6}: commit: 原来删除文件也是要git rm 的
0f8acc5 HEAD@{7}: commit (amend): add the first version repo
270c15c HEAD@{8}: commit: add the first version repo
fa98849 HEAD@{9}: commit (initial): wrote a reademe file.

工作区和暂存区

工作区

就是当前git仓库的目录,git init命令执行的时候定义的目录,及其子目录。

暂存区

当我们改动完成后,执行git add xxx的过程,就是把改动添加到暂存区的过程。

git commit的过程,可以看做是对当前工作区做了一个快照,实际上是将暂存的内容都提交到当前分支的过程。

git commit执行完成后,对Git来说,我们的一次修改才算完成。我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,git commit就是往master分支上提交更改。

或者说,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。

Git修改的概念

Git管理的是每一次的修改,举个栗子,流程如下:

change1 --> git add # 记录change1并放暂存区

change2 --> git commit # 此时change2并没有被存放到暂存区,提交的只是change1

两次修改都完成了,但是因为暂存中只有change1,所以提交的时候会只提交暂存区中的内容,change2的改动也会被检测到,要想提交change2的改动,只需要git add xxx ,然后git commit即可。

如果不想提交的那么频繁,可以先git add多次,然后合并一起git commit。合并提交时,同一个文件的多次改动也会被合并为同一个修改。

两次git add 之间的改动才会被记录为一次修改。

同理,你不git add,那么commit的时候就不会被记录到分支中。

撤销修改

git checkout -- file:这个命令可以丢弃工作区的更改,使文件回退到最近一次commit或最近一次add的状态。

比如当前文件已经修改并且add了之后,再次进行了错误的修改,并且没有add,那么我们可以使用

git checkout -- file命令来丢弃当前工作区中已保存但没有git add的改动,使之回退到最后一次add的状态。

git 2.23以后,git checkout被拆分成了两个命令:git switchgit restore

git checkout 这个命令承担了太多职责,既被用来切换分支,又被用来恢复工作区文件,对用户造成了很大的认知负担。
于是,在 Git 2.23 里,这个命令被拆分成了两个新命令:git switch 和 git restore

远程仓库推送

github注册及创建仓库的部分不再赘述。

添加远程仓库关联:

1
git remote add origin git@github.com:hcl01125/learngit.git

把本地仓库推送到远程库:

1
git push -u origin master

这样就把本地的master分支推送到了远程origin仓库的master分支。

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

一点小意外:

由于之前定义git工作目录的时候,直接把练习java的文件夹当成了工作目录,没注意里面有个之前编译的aria2c的二进制文件,比较大,有100多M,所以git push的时候就失败了,提示超过100M限制。

解决办法:

先删除文件aria2c,然后将提交记录中aria2c的部分删掉即可,此操作不影响工作区内容,也不影响其余文件的commit记录。

1
2
3
4
5
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch aria2c' --prune-empty --tag-name-filter cat -- --all

————————————————
版权声明:本文为CSDN博主「Mr.True」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/q258523454/article/details/83899911

然后再提交就没问题了。

顺便解决使用https协议推送github时,每次都需要输入账号密码的问题:

在项目目录下输入:

1
git config --global credential.helper store

这样当你下一次输入账号密码之后,就会在本地保存你的账号密码,就不用每次都输入了。

分支管理

分支的概念

假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

创建与合并分支

Git中,HEAD指向的分支就是当前分支,master分支是要提交的主分支。

每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上。

每次commit之后,HEAD指向的分支会向前推进一步,我们只需要在需要的地方开启新的分支,然后在新分支上继续开发,等到开发完毕,再合并回master分支即可。

在原来的分支上创建dev分支:

1
git checkout -b dev

git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:

1
2
3
$ git branch dev
$ git checkout dev
Switched to branch 'dev'

然后,用git branch命令查看当前分支:

1
2
3
$ git branch
* dev
master

git branch命令会列出所有分支,当前分支前面会标一个*号。

然后我们可以在dev分支上继续开发,开发完成之后commit,此时改动并不会被同步到maser分支上。

如果需要将dev分支合并回master分支,在master分支下使用命令:

1
git merge dev

git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。

注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。

除了Fast-forward之外,也还有其他的合并方式。

合并完成之后,就可以删除dev分支了:

1
git branch -d dev

删除后,查看branch,就只剩下master分支了:

1
2
$ git branch
* master

分支管理相关命令:

查看分支:git branch

创建分支:git branch

切换分支:git checkout 或者git switch

创建+切换分支:git checkout -b 或者git switch -c

合并某分支到当前分支:git merge

删除分支:git branch -d

解决分支冲突

这是很常见的一种情况,当两个分支分别开发,合并的时候,难免会遇到改动冲突的时候,这时候就不能直接合并,而是需要手动解决了冲突之后才能合并成功。

准备新的feature1分支,继续我们的新分支开发:

1
2
$ git switch -c feature1
Switched to a new branch 'feature1'

在feature1分支中对本文进行修改,然后commit

然后切换回master分支中修改,并commit

这样,master分支和feature1分支各自都分别有新的提交,这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,所以当我们执行git merge的时候,Git会提示冲突,并让我们手动解决冲突的文件后再合并。

此时文件中会将两个分支的改动都写入并标注出来,我们只需要手动将两个分支的改动按照需要修改,再git commit即可。

这个时候,由于我们手动解决了冲突,并commit了,所以当前分支的版本较另一分支新,所以我们再合并的话就不会提示冲突了。

最后,删除feature1分支即可。

merge --no-ff

合并分支的时候,Git会默认使用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。

如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

对于dev分支,Fast forward模式合并情况:

image-20200222194330670.png

加参数--no-ff时的分支合并情况:

0

分支管理策略

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

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活

那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

所以,团队合作的分支看起来就像这样:

2

bug分支

git stash

假如目前还有未完成的开发工作,但是临时需要新开一个分支来处理buggit提供了stash的功能,可以把当前的工作现场储存起来,等以后恢复现场后继续工作。

git stash之后,当前暂存区的内容会被暂时储存起来,可以使用git stash list查看,恢复的命令如下:

git stash apply:恢复后,stash内容并不删除,你需要用git stash drop来删除;

git stash pop:恢复的同时stash内容也会被删除。

也可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:

1
$ git stash apply stash@{0}

git cherry-pick

假如目前发现了bug,然后新开了个分支fix1修复了,并且commit到了master分支,但是dev分支是在bug修复前开的,要想将dev分支的bug也修复了,就需要将fix1提交的改动复制到dev分支。

为了方便操作,Git专门提供了一个cherry-pick命令,让我们能复制一个特定的提交到当前分支:

1
git cherry-pick <commit id>

Git自动给dev分支做了一次提交,注意这次提交的commit1d4b803,它并不同于master4c805e2,因为这两个commit只是改动相同,但确实是两个不同的commit。用git cherry-pick,我们就不需要在dev分支上手动再把修bug的过程重复一遍。

有些聪明的童鞋会想了,既然可以在master分支上修复bug后,在dev分支上可以“重放”这个修复过程,那么直接在dev分支上修复bug,然后在master分支上“重放”行不行?当然可以,不过你仍然需要git stash命令保存现场,才能从dev分支切换到master分支。

feature分支

当需要添加一些实验性质的代码时,又不希望将master分支搞乱,所以我们需要新开一个”feature“分支。

首先我们的开发都是在dev分支下,所以feature分支是在dev分支下新开的,则会继承dev分支的改动。

在新开的feature分支上完成开发后,一切顺利的话我们可以合并到dev分支,然后合并到master分支,完成改动。但是如果发生问题,我们在feature分支下完成的开发需要被放弃,那我们可以直接删除feature分支。

执行git branch -d feature,Git会提示销毁失败,feature分支还没有被合并,所以我们需要用git branch -D feature强制删除。

feature分支的需求本质是需要删除一个还没有被合并的新分支,使用git branch -D

这里还有一个小知识点:没有commit的情况下,暂存区中的内容不属于任何一个分支。

多人协作

多人协作时,需要同步的分支有两个,一个是master分支,一个是dev分支。当有新需求的时候,每个组员都开新分支解决需求,然后合并到dev分支中。dev分支是用来同步开发进度的,一般情况下,master分支只发布经过严格测试之后的版本,且需要专人从dev分支中合并,以免污染master分支。

当远程的版本比本地新的时候,需要用git pull抓取远程版本,然后在本地合并。

  • 查看远程库信息,使用git remote -v
  • 本地新建的分支如果不推送到远程,对其他人就是不可见的;
  • 从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;
  • 在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;
  • 建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name
  • 从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。

Rebase

多人在同一个分支上协作时,很容易出现冲突。即使没有冲突,后push的童鞋不得不先pull,在本地合并,然后才能push成功。

Rebase的使用情景:

假如此时本地进行了修改,然后当你push到远程的时候,有人先你一步push了,你需要先git pull,合并远程的提交,然后再push。但是此时你的git log会增加一个远程pull下来的commit,然后和本地新增的部分形成一个提交历史的分叉,就像这样:

1
2
3
4
5
6
7
8
$ git log --graph --pretty=oneline --abbrev-commit
* e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit
|\
| * f005ed4 (origin/master) set exit=1 # pull下来的别人的commit
* | 582d922 add author # 本地commit1
* | 8875536 add comment # 本地commit2
|/
* d1be385 init hello # 远程库HEAD

要想将分叉变成一条直线,执行命令git rebase,就可以将弯的变直了:

1
2
3
4
5
6
$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master) add author
* 3611cfe add comment
* f005ed4 (origin/master) set exit=1
* d1be385 init hello
...

实际上是将本地的两次提交和远程pull下来的提交交换了位置,然后提交历史就变成了一条直线,使得查看起来更直观。

然后再push,再用git log看看效果:

1
2
3
4
5
6
$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master, origin/master) add author
* 3611cfe add comment
* f005ed4 set exit=1
* d1be385 init hello
...

远程的提交历史也变直了。

标签(tag)

tag相当于某次commit的别名,为了下次找到某次commit时更方便而设立的。

例:git tag v1.0

默认标签是打在最新提交的commit上的,如果要为选定的committag,则使用对应的commit id

例:git tag v0.9 f52c633

查看所有标签:git tag

查看标签信息:git show <tagname>

创建带有说明的标签,用-a指定标签名,-m指定说明文字:

git tag -a v0.1 -m "version 0.1 released" 1094adb

删除标签:git tag -d <tagname>

推送某个标签到远程:git push origin <tagname>

一次性推送全部尚未推送到远程的本地标签:git push origin --tags

删除远程标签:先从本地删除(git tag -d),然后从远程删除(git push origin :refs/tags/v0.9