git 学习笔记2
Browsing History
Getting a Repository
下载: 链接:https://pan.baidu.com/s/12cWs65AEMGUvw1pr6yRI3A 提取码:dpyi
Viewing the History
我们之前知道了 git log
可以查询详细信息, git log --oneline
可以查看简略信息。那么如果 git log --oneline --stat
就可以看到不同的commit的信息和它所做的修改。
比如最后一次提交,一共有五个文件改变了,一共有7个地方增加了,4个地方减少了。其中audience.txt 有三处地方增加了,一处地方减少了,以此类推。
git log stat
可以展示每一个提交的详细信息
git log --patch
可以看到每一个提交具体改变的内容,如下:
Filtering the History
当项目的时间越来越长,内容越来越多的时候,我们不会想查看所有的commit,我们只想筛选符合我们条件的commit
我们能通过作者名字、commit日期、commit message, 内容等来进行筛选。
比如我们想筛选倒数后三个commit,可以用 git log --oneline -3
比如我们想筛选出作者为Mosh的commit,可以用 git log --oneline --author ="Mosh"
又比如我想筛选出日期在 2020年8月17号之后的commit,可以用 git log --oneline --after ="2020-08-17"
甚至可以这么写git log --oneline --after ="one week ago"
如果想要筛选 git message ,那么可以用命令 git log --oneline --grep = "GUI"
那么Git就会筛选出 messages中包含 “GUI” 的内容。注意,这是区分大小写的!
如果我们要查询commit中的内容,可以用 git log --oneline -S"OBJECTIVES"
来筛选出commit中对OBJECTIVES进行修改的commit
如果我们想看看哪些commit对一特定文件进行修改的,我们可以用命令 git log --oneline toc.txt
(toc.txt是目标文件)
当我们想看看这些commit的增删时,我们要注意 —patch 不能写在文件名的后面,否则会发生歧义。要这样写
git log --oneline --patch toc.txt
Formatting the Log Output
我们可以对 log 日志进行一些格式化输出:比如 %Cgreen 就是把字体颜色换成绿色,%an 就代表 author,%Creset 就是恢复默认颜 色。%h 就是精简ID,%H是完整ID, %cd代表日期。
git log --pretty=format:"%Cgreen%an %Creset committed %h on %cd "
我们可以从 git官网 中查看格式化日志的其他占位符
Aliases
现在我们对一个log格式化字符串取一个别名.我们可以使用命令
git config --global alias.lg "log --pretty=format:'%an committed %h'"
用 git config --global -e
查看 gitconfig 我们会发现
现在我们就可以用 git lg
来显示我们自定义的日志了。
同理,我们可以设置一个unstage命令,将staging area中的数据还原。
git config --global alias.unstage "restore --staged ."
Viewing a Commit
在 git基础 中我们讲过 git show HEAD~n-1
,其实 它完整的用法是这样的:
1 | usage: git log [<options>] [<revision-range>] [[--] <path>...] |
如果我不想看有哪些改变,我想改这个文件在这个commit中的内容。我们可以git show HEAD
+文件相对路径
git show HEAD~2:sections/creating-snapshots/staging-changes.txt
如果我们只想看看这个commit中有哪些文件做了修改,我们可以
git show HEAD~2 --name-only
我们发现在sections中的creating-snapshots文件夹中的staging-changes.txt
`git show HEAD~2 --name-status
则会显示在这个commit当中文件的增改 ,比如下面 sections/creating-snapshots/staging-changes.txt
是被修改的.
Viewing the Changes Across Commits
现在我们来思考一个问题,怎么才能看多次 commits中文件做出的改变呢?
我们使用 diff 命令
git diff HEAD~2 HEAD audience.txt
当然也可以 git diff HEAD~2 HEAD --name-status
来查看现在到过去两个版本之间变动过的文件
Checking Out a Commit
有时候我们想要看看某一个commit下所有的project文件,那么我们就需要将commit 复制出来然后粘贴到working directory.
我们现在选一个ID来checkout git checkout dad47ed
,再次git log --oneline
后发现
这里我们要理解HEAD和Master这两个概念
我们的commits就像一条链一样,一环指着一环的,MASTER始终指着最后一个commit,HEAD指向Master,这有了之前最后一个commit是这样表示:
a642e12 (HEAD -> master) Add header to all pages.
现在我们check out一个commit,其实就是移动了HEAD指针,现在HEAD指向我们的目标commit了.
但是这样需要注意, 现在不要再进行commit操作了,因为commit会加在head指向的地方,也就是说现在如果commit的话会发现变成了这样.
那么当我的HEAD再次指向MASTER之后,新来的commit将永远不会被访问到了.我们要避免这种情况发生
如果我们想要让HEAD再次指向MASTER,之间git checkout master
即可
Finding Bugs Using Bisect
Finding Contributors Using Shortlog
有些时候我们需要找到所有commit过的人.我们可以使用命令
git shortlog
我们可以
git shortlog -n -s
git short log -n -e
来显示这个人的邮箱
甚至可以 git shortlog -n -s -e --before="" --after=""
来框定某一个特定时间内提交者的信息
Viewing the History of a File
现在我们来看看一个文件在历次commits之间的变化情况.
git log --oneline toc.txt
如果我们想看看这个文件在每次commit之中改了什么内容,可以用git log --oneline --patch toc.txt
Restoring a Deleting File
我们有时候会误删一些文件,我们可以通过git来恢复.
我们先删除 toc.txt 也就是 git rm toc.txt
然后我们git log --oneline toc.txt
却发现报错了:
1 | fatal: ambiguous argument 'toc.txt': unknown revision or path not in the working tree. |
这是因为在删除了toc.txt之后,git就不能辨别 toc.txt 到底是命令还是一个文件了,我们需要 用 — 来分隔文件和命令
git log --oneline -- toc.txt
我们要的是 a642e12 这个commit,然后我们用命令 git checkout a642e12 toc.txt
将 commit 中的 toc.txt 取出放到 working directory.
Finding the Author of Line Using Blame
如果我们觉得某一个人的某个文件代码写的很差,我们可以使用 Blame 功能 “责备他”
git blame audience.txt
-e 即查看他人的邮件
git blame -e audience.txt
-L 1,3 即显示前三行
git blame -e -L 1,3 audience.txt
(感觉这个功能也没啥用处…)
Tagging
我们可以给一些有里程碑意义的commit打标签
比如 git tag v1.0 a642e12
我们就发现倒数第二个commit被打上了 v1.0的标签
设置了 tag之后,checkout会变得非常方便了 ,直接 git checkout v1.0
就可以了。
这个tag可以看作是指针或者一个commit的别名。
还有一种 tag叫做 annotated tag. 这种tag是一个对象,里面有打tag的人的email之类的信息,比如
git tag -a v1.1 -m "My version 1.1"
如果我们要查看每个tag的 message,那么我们可以用 git tag -n
查看anotated tag的tageer的信息,可以使用 git show tag名
,比如:
查看现在定了几个tag可以用git tag
删除某个tag可以用 git tag -d v1.1
这样的命令,那么在开头就会显示Author的信息和打标签的时间了
Browsing History Using VSCode
用 Gitlens 会更加方便
Browsing the History Using GitKraken
Branching
- Use branches
- Compare branches
- Merge branches
- Resolve conflicts
- Undo a faulty merge
- Essential tools(stashing ,cherry picking )
What are Branches
我们可以把 Branch 看成是 独立的分隔的 workspace
我们有一个working space MASTER,又有另外一个working space FEATURE 用来研究其他的特性。在FEATURE完成之前,我们不想让里面的代码对MASTER进行一个干扰。当FEATURE已经完善之后,我们再将其和MASTER进行合并。
branch 这个功能让我们能够对不同的对象进行不同的工作,两者互不干扰,因为我们需要保证 main line 尽可能稳定,以便branch能够随时发布,同时也有助于团队的新成员能尽快上手项目。
Git中的Branch只是一个对commit的指针。Master只是一个指向 main line 最后一次commit的指针。当我们commit之后,git会自动移动 Master 指针。当我们新建了一个branch之后,我们实际是新建了一个指针,这个指针占有的内存很小。那么怎么才能知道当前我们是在那个 working space当中呢?git中还有一个指针HEAD,这个指针指向一个branch的名字,我们可以用 checkout 将HEAD指向不同的branch。
Working with Branches
比如我们现在发现了一个Bug,现在为了修复这个bug,我们新建一个branch叫做 bugfix
- 使用命令
git branch bugfix
就可以创建branch 使用命令
git branch
可以查看当前的branches如果要切换branches,可以使用
git switch bugfix
或者git checkout bugfix
- 如果要重命名branch ,可以使用
git branch -m bugfix(原名) bugfix/signup-form(新名)
我们对audience.txt 进行了改变并且提交了。然后查看日志,会发现 master是bugfix之前一个版本。
我们对audience做出的改变只能在bugfix这个commit 下才能查看。如果我们切换成master一支,我们是不会发现audience.txt做出了什么改变的。
- 在切换回 master之后,我们是看不出bugfix的,如果要查看我们就要用
git log --oneline --all
- 如果我们已经成功修复了bug,需要和master合并然后删去这个bugfix branch,我们可以用命令
git branch -d bugfix/signup-form
但是要注意,如果没有merge之前就删除这个branch会报错。如果我们真的要强制删除,我们可以使用git branch -D bugfix/signup-form
也就是变成大写的D
Comparing Branches
当我们创建branches并对其commit时,我们需要看看它们是怎么与master主线岔开的。我们现在就来比比不同的branches。
我们可以用 git log master..bugfix/sigup-form
来查看哪些commit是在这条branch上递交的
如果我们不想看commits,而是看在branch上改变了哪些内容,我们可以这么写:
git diff master..bugfix/signup-form
或者git diff bugfix/signup-form
如果我们只想看看那些文件名改变了我们可以用这个命令
git diff --name-status bugfix/signup-form
Stashing
stach有存放之意。当我们在master中做了一些改变,现在却想切换到branch去,那么branch中的内容就会覆盖掉我们现在working directory中的内容。
这就是stash出现的场景了,我们需要用stash方法将当前的working directory保存起来,然后当我们处理完 branch的事情、切换回branch之后在取出来。
git stash push -m "New tax rules."
将当前的内容stash,-m 后填写说明
但是我们要注意, git stash是不会将git 没有追踪的(untracked) 文件保存下来的。也就是说不会stash刚刚创建的文件
那么为了解决上面的问题,我们可以使用
git stash push -a -m"My new stash"
-a 就代表了
-all想看看当前的stash信息,可以使用
git stash list
现在假设我们再bugfix这支branch上的工作已经完成,我们切回master,想看看之前保留在stash中的文件做出了哪些改变 ,可以使用
git stash show 1
来看stash@{1}
中的内容如果我想将stash中的文件放回到working directory,那么可以用
git stash apply 1
来提取stash@{1}
中的内容。如果我们已经不需要stash了,可以用
git stash drop 1
来删除stash@{1}
可以用
git stash clear
删除所有的stash
Merging
git提供了三种 commit 操作。
Fast-forward (if branches have not diverged)
就是master和branch是一条链上的。
如图所示,Master和BUGFIX 这支branch是线性关系,那么我们只需要将 MASTER 指针指向BUGFIX指向的commit,再将BUGFIX删除就能完成 合并操作。
Fast-forward merge 属于“快进方式”,不过这种情况如果删除分支,则会丢失分支信息。因为在这个过程中没有创建commit
no-ff merge
和Fast-forward merge的初始情况相同,但是no-ff merge在合并的时候并不是只将MASTER指向branch末尾,而是新建了一个commit
3-way(if branch have diverged)
master 和 branch 分叉了,各自有commit
现在我们执行合并操作的话,就是新建一个commit,将master和BUGFIX文件各自做出的改变合并起来。
Fast-forward Merges
学了branch以后我们在查看log的时候记得加上 —graph, 这样git会以点线图的形式将master和branch所做的变更和合并呈现出来。这里展示的是Fast-forward , branch和master是一条链上的,所以graph是线性排列的。
- 我们使用
git merge bugfix/signup-form
在HEAD指向MASTER的时候来实现Fast-forward Merge
再次查看log,发现 master和branch(bugfix) 都指向了一个commit了
No-ff merge
同样是线性的branch和master,我们也可以选择不是 fast-forward merge的形式(no-ff)来进行合并。上文说过,Fast-forward merge 并没有创建新的commit,容易丢失 branch版本。若选择 no-ff 形式,在合并时会新建一个commit,这样就会保存branch的版本了。
接下来我们模拟 no-ff 合并操作。
- 可以用
git switch -C branch名字
将创建和切换branch 缩在一条命令当中
现在有一个master,一个bugfix/login-form 的branch
- 我们使用
git merge --no-ff bugfix/login-form
这一命令来执行 no-ff 类的merge操作。
查看merge做的点线图,我们可以很清楚的看到merge和master进行了一个合并且生成了一个新的commit
我们既要习惯fast-forward,也要习惯no-ff ,因为no-ff commit merge可以反映出一个项目创建后的一步步历史,而且no-ff merge能让在不想要branch的时候更方便地退回之前的master版本。
如果我们不喜欢(或者上司不喜欢) fast-forward 的话,我们可以把 no-ff 设置为全局变量,即对所有的repository通用。
git config ff no
(对当前repository) /git config --global ff no
对所有repositories
Three-way Merges
现在我们来模拟 Three-way Merge 的操作。
流程:创建并切换至新branch,commit一次;切换回master,commit一次 ,我们就会发现branch和master发生了分叉。
现在我们要合并这两支,这两支有着共同的祖先。
- 我们可以直接用
git merge feature/change-password
来进行 three-way merge
我们看到下面git给出的路径,已经对两支进行了合并操作。
Viewing Merged and Unmerged Branches
有时候我们新建了一个branch,但是项目做着做着可能就忘记将这个branch合并了,于是我们可以通过命令
git branch --merged
来查看哪些branch已经合并了git branch --no-merged
来查看那些branch还没有进行合并
对于已经合并的branch我们要养成好习惯及时删除。
git branch -d bugfix/login-form
Merge Conflicts
在Merge的时候我们常常会遇到冲突
- 当一个文件同时被两支branch修改时
- 一个文件在一支里面被修改,却被另一支删除时
- 同一文件在两个分支中都加了点东西,但是加的内容不同
遇到这些冲突的时候,git就会暂停合并,我们需要告诉Git我们想怎么选择。
我们现在通过git命令来模拟这一操作:
1 | git switch -C bugfix/change-password |
现在我 git merge bugfix/change-password
之后,会出现conflicts
这时候git就会自动跳到一个 master|MERGING 状态。在这个状态下我们输入 git status
就会发现change-password.txt 这个文件并没有被merge,因为它被 both modified 了。
这时候,我们可以用vscode打开 change-password.txt
vscode可以使用 “采用当前更改(master)”、”采用传入的更改(branch)”、”保留双方更改”、”比较变更”这几种选项。
或者我们也可以直接手动更改再保存(但是尽量别再自己加东西了!)
修改好以后,我们再使用 git add .
和 git commit
将定稿commit
Graphical Merge Tools
VScode用来进行合并操作有点麻烦。这里介绍一些其他的工具
- Kdiff
- P4Merge
- WinMerge(Windows Only)
p4Merge 的下载地址https://www.perforce.com/downloads/visual-merge-tool
下载完成后不需要点击软件,需要在git中进行配置
1 | git config --global merge.tool p4merge |
然后当文件发生冲突的时候,我们可以用命令
git mergetool
来打开 p4merge界面
我们看到这就是 P4Merge的UI,操作很友好,支持不同conficts文件之间的切换。
Aborting a Merge
我们进行Merge操作的时候会遇到一些conflicts,但是当我们犹豫不决、没下定决心取舍哪个版本的时候。我们可以终止merging状态,回到合并之前的样子
- 只要使用
git merge --abort
即可
Undoing a Faulty Merge
有时候我们在Merge之后发现code没办法得到兼容或者编译,那么我们现在需要回退到之前的状态、取消当前的merge状态。待修改完成后,我们再重新merge
现在是这样子的。我们要撤回当前的merge状态,有两种选择:
git reset 是将 merge 后的commit删除(remove),
git revert是用于“反做”某一个版本,以达到撤销该版本的修改的目的。现在我们的merge操作出现了点问题,但是我想保留这个commit,又想回退到上一个版本,这时候我们可以新建一个版本,这个版本和merge前的哪个版本一模一样,达到了退回的目的。
首先我们来看 remove操作: 示意图如下
一开始的状态时这样子的
将Master 回退到之前状态后,没有任何指针指向最后一个commit,git会自行删除这个commit
git reset --hard HEAD~1
这行命令就可以完成 remove操作
HEAD~1就代表当前HEAD之前一个版本的commit,但是hard怎么理解呢?
当我们执行 reset操作的时候,我们有三种选择:
- soft
- mixed
- hard
当我们执行soft的时候,Last Snapshot 会回退到上一个版本,但是 Working directory 和 staging area中的内容是不会受影响的,也就是还和原来保持一样
当我们使用 mixed 操作的时候,git会取出上一次的commit的snapshot然后放到 Staging area 当中去。
hard 操作则是将快照应用在三个地方:
执行完命令后,发现log已经变成这样了:
我们可以再通过 git reset --hard merge编号
来重新变成merge之后的状态
下面来介绍 revert 操作,再revert的时候我们需要告诉git回退到哪一个版本,是master支上的还是 branch上的
git revert -m 1 HEAD
-m 1 就代表着 first parent,也就是master上的版本。
重新查看 log,可以发现新建了一个commit,这个commit和之前的9455172 是一模一样的
Squash Merging
Squash 是挤压之意,相比 merge 来说会减少分支合并的记录,会被压缩为一条 commit 记录。squash merge 本质是把 dev 分支的改动保存到 master 本地,接下来还需要手动提交一下。
我们先模拟当前的状况
1 | git switch -C bugfix/photo-upload |
然后我们进行 squash merge
- 首先
git switch master
git merge --squash bugfix/photho-upload
这还没完,这只是将branch 中修改的内容存放在staging area当中,还没有commit
git commit -m "Fix the bug one the photo upload page"
以后
但是要注意,squash merge的应用情况是修改一些小bug,或者增加一些小feature,不宜修改太多。当我们commit之后也要及时删除。因为如果project很大的话,我们会忘记这支branch,git branch --no-merged
就会看到branch还未合并,可能会出现误解。
git branch -D bugfix/photo-upload
可以删除这支branch。注意,这里不能写 -d,因为git会认为这支branch还没有合并,从而拒绝删除。 -D则是强制删除。
Rebasing
Rebasing 顾名思义就是将branch的Base重定向。
下面是初始状态:
Rebase之后,我们将FEATURE这支转移到Master上
最后将Master和Feature合并
Rebase 是一种修改Commit 历史的写法。我们不应该在多人团队协作的情况下擅自使用 Rebase
事实上,在Git 内部的实现原理如下:
首先在Master后面创建两个F1,F2 commit的拷贝F1*和F2*。然后再让Feature指向 F2*, 这时没有指针再指向F1、F2 了,git会自行删除它们。
我们先用命令创造出这样一个情况 :
然后我们将Head指向 feature/shopping-cart 并 git rebase master
最后我们只需要做一下 fast-forward merge 就可以将两个合并起来
1 | git switch master |
那么如果rebase发生冲突了该怎么办呢?
我们同样可以通过 git mergetool
来进行调整。
一个文件调整完了,可以用
git rebase --continue
来进行下一个文件的处理可以用
git rebase --skip
来跳过当前文件的处理。- 可以用
git rebase --abort
来回暂停rebase操作。
Cherry Picking
Cherry Picking 就是像在两个樱桃中选择一个去摘。在git中如图:
我们只要记下 F1 的ID,然后 git cherry-pickl ID
如果发生了问题,那么我们也可以用 git mergetool
来解决。
Picking a File from Another Branch
现在我们来看看怎么从一支branch中取出单独的一个文件放到当前的working directory
1 | git switch -C feature/send-email |
我们先 git switch master
然后
git restore --source=feature/send-email -- toc.txt
也就是从 feature/send-email这一支的最新commit中选出toc.txt 文件放到working directory当中- 最后 add ,commit 即可