git学习2

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
2
usage: git log [<options>] [<revision-range>] [[--] <path>...]
or: git show [<options>] <object>...

如果我不想看有哪些改变,我想改这个文件在这个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
2
3
fatal: ambiguous argument 'toc.txt': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

这是因为在删除了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
2
3
4
5
6
7
8
9
git switch -C bugfix/change-password
code change-password.txt
git add .
git commit -m "Update change-password.txt"

git switch master
code change-password.txt
git add .
git commit -m "Update change-password.txt"

现在我 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
2
3
git config --global merge.tool p4merge

git config --global mergetool.p4merge.path "C:\Program Files\Perforce\p4merge.exe"//exe所在的目录

然后当文件发生冲突的时候,我们可以用命令

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
2
3
4
5
6
git switch -C bugfix/photo-upload 
echo bugfix >> audience.txt
git commit -am "Update audience.txt"

echo bugfix>>toc.txt
git commit -am "Update toc.txt"

然后我们进行 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
2
git switch master
git merge feature/shopping-cart

那么如果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
2
3
git switch -C feature/send-email
echo river > toc.txt
git commit -am "Update toc.txt"
  • 我们先 git switch master

  • 然后 git restore --source=feature/send-email -- toc.txt 也就是从 feature/send-email这一支的最新commit中选出toc.txt 文件放到working directory当中

  • 最后 add ,commit 即可
-------------本文结束,感谢您的阅读-------------