Contents

Git的基本使用

基础(最常用的命令)

1
2
3
4
5
6
7
8
git init #初始化Git仓库
git add . #添加所有未追踪文件或修改
git add filename #添加指定文件
git commit -m "commit information" # 提交
git push #推送到远程
git pull #从远程拉取
git status # 查看当前状态
git log # 查看提交日志

版本回退

前一个版本:

I’m a little tired!!!

当前版本:

I’m not tired!!!

这时使用 git addgit commit 提交了, 但是想回退过去的操作办法如下:

  1. git中HEAD表示当前的提交版本, HEAD^表示前一个提交版本,所以想回退的操作为: git reset --hard HEAD^

    未回退命令时日志显示: 有两个版本: first 和 not tired

    使用回退命令后日志显示: 只有 first 了

  2. 当知道commit ID时, 使用 commit ID直接跳到想要的版本.

    比如现在想再返回 not tired, 操作命令为: git reset --hard commit_id

    这时再查看log结果为:

    可以看到, 使用了这个命令后, 我们的not tired 提交又回来了.

  3. 当不知道commit ID时也是有办法的

    git提供了 git reflog 命令, 这个命令的输出结果是记录你的每一次命令, 使用 git reflog 命令查看历史命令:

    可以看到第一列记录了所有的commit ID, 最后一列记录了执行的操作, 根据最后一列信息找到对应的ID号即可

回退问题:

  • 如何跳到某个指定commit_id的版本?
  • 假如你刚提交了一个版本, 现在后悔了想返回到前一个版本, 应该使用什么命令?
  • 假如你回退到前某个版本, 现在后悔了, 想返回到最近的某个版本, 但是 commit_id 不记得, 该怎么办?

回退总结:

  • HEAD 为当前版本, 使git reset --hard commit_id 可以跳到对应的版本

  • git reflog 命令可以查看所有操作的记录, 可以用于查找所需要的版本号

  • tip: HEAD表示当前版本, HEAD^表示前一个提交版本, HEAD^^为前2个提交的版本, HEAD~100 为第前100个版本

撤销修改

前一节所说的版本回退有个缺点,

那就是, 回退到前一个版本, 所有相关文件的最新修改均会丢失.

并且版本回退是针对已提交的内容的.

现在有个新问题: 在git管理的版本下, 撤销对某个文件的修改.

这之前要了解一下git管理的基本逻辑.

git 基本逻辑

git仓库其实分为工作区(working tree), 暂存区(stage), 和 仓库()

工作区就是我们看到和使用的部分

暂存区是我们使用 git add filename 后文件所到达的部分

仓库是我们使用 git commit 后文件所在的部分

在使用 git commit 命令时, 只会将暂存区部分的内容保存到仓库

文件修改的撤销

  1. 在上一次提交后, 本次我们对某个文件进行了修改, 发现修改后程序运行出错了.

现在删除对文件的修改, 应该怎么做(并未使用 git add命令)?

这时需要的命令为 git checkout -- filename.

对文件修改后查看工作区状态:

可以看到有修改, 这时使用 git checkout filename 后结果为:

再次查看文件, 修改己经没有了

  1. 假设这次我们对文件进行了修改, 且已经使用了 git add filename了该怎么办呢?

办法是使用 git reset HEAD filename 记其回到未 add 的情况.

我们将修改添加到暂存区, 并查看状态:

先看一下与前面的区别, 未add时, 字是红色的, 提示信息是"尚未暂存以备提交的变更"

使用 git add filename 后提示信息是"要提交的变更"

此时使用 git reset HEAD filename 后的状态为:

可以看到取消了暂存的变更, 这时查看状态则又到了修改未提交的状态了.

此时再使用 git checkout -- filename 命令即可以撤销修改了.

撤销问题

  • 有修改, 但是未添加到暂存区, 如何撤销?
  • 有修改, 已添加到暂存区, 如何撤销?

撤销总结

  • git checkout -- filename 撤销对工作区文件的修改
  • git reset HEAD filename 撤销对文件的暂存操作

文件删除

git仓库的文件删除和正常文件夹的删除不大一样, 因为git将删除也视为了一个操作, 因此删除也需要提交. 正常删除文件后查看仓库状态:

同样, 使用 git checkout -- filename也可以取消该操作.

使用 git rm filename 删除文件后查看状态:

可以看到, 直接删除后提示信息是红色的, 且提示信息是"修改尚未加入提交".

也就是说, 这时还需要使用 git add命令才可以使用 git commit将删除操作提交到仓库.

git rm filename则可以直接将删除提交到仓库.

对于已添加,未添加的修改如何撤销可见上一节.

删除问题

  1. 若要删除仓库中的文件应如何操作?
  2. 直接删除与使用 git rm 命令删除有何不同?
  3. 删除后如何撤销?

删除总结

  1. 删除与修改一样, 都是要提交到仓库的
  2. 使用 git rm 操作相当于使用 直接删除并使用了 git add 命令
  3. 删除的撤销可根据是否添加到暂存区同修改一样操作

分支

创建与合并

  1. 分支的创建与删除: git branch branch_name

    创建新分支前:

    创建新分支后: git branch newBranch (* 表示当前HEAD所在的分析, 即当前工作区的分支)

    切换分支: git checkout newBranch 可以看到 * 从main到了newBranch

    分支删除: git branch -d newBranch (不能删除当前所在分支, 因此要先切回main分支才可执行该操作)

  2. 分支的合并现有两个分支:main和bugFix 合并前:

    将HEAD放在main分支上, 并执行 git merge bugFix:

    可以看到, 合并会产生一个新的提交, 且当前节点有了两个父节点.

    这里其实可以更深入理解下git checkout这个命令, 它的真实作用是切换当前工作区在git这个仓库树中的位置.即, HEAD可以指向main或者bugFix这种具体的分支名, 也可以指向某一个commit_id(每一个commit_id即是工作区的一个版本)

冲突的修正

假如我们现在有2个分支: master和newBranch, 在这两个分支中我们都对一个文件进行了修改. 这时, 将newBranch分支合并到master分支时就会出现冲突:

这里master分支中我们添加的内容是"Creating a new branch!!!“并提交了.

在newBranch分支中我们添加的内容是"Create a new branch!!!”.

所以在HEAD处于master分支, 并运行 git merge newBranch时会出现冲突.

我们打开提示的冲突文件, 文件内容如下:

Git用<,=,>标记出不同分支的内容,我们修改如下后保存:

这时查看仓库状态为:

此时将其添加到仓库并提交

这时查看日志树可以看到合并过程:

分支问题

  1. 如何创建分支与删除分支?
  2. 如何在不同分支中进行切换?
  3. 如何合并不同的分支?
  4. 合并分支后有冲突了该怎么办?

分支总结

  1. 分支创建: git branch branch_name
  2. 分支删除: git branch -d branch_name
  3. 将其他分支合并到当前分支: git merge other_branch
  4. 分支切换: git checkout branch_name
  5. 冲突解决: 修正冲突的文件并添加到暂存区, 然后提交

高级篇:命令详解

分离HEAD

这是一个很小的概念, 单独说它, 就是因为它很重要.

其实git是由不同的提交组成, 每个提交都可以看成一个树中的节点.

所有的提交在一起组成了一个提交树.

而当前的工作区(也就是我们能看到并修改的那些文件), 就是HEAD所指向的.

其实HEAD并不是非要指向具体的分支, 它也可以直接指向某个提交.

比如当前的提交树为:

可以看到*在bugFix这个分支上, 这也表示HEAD指向bugFix, 我们可以直接让它指向C4 git checkout C4 (这里的C4是提交id):

同样, 也可以使用这个命令让它指向 main, 或者 C0:

其实checkout这个命令用来切换HEAD的指向并不准确, 新版的git使用switch命令

相对引用(^)

  1. 在不同的仓库进行切换时, 使用commit id十分麻烦, 因为id需要使用git log命令查找, git提供了相对引用, 即^表示上一个, ^^表示上2个, ~10 表示前10个.

    如使用 git checkout main^ 效果为:

    /ox-hugo/k7l8fp.png/ox-hugo/Vopopj.png

    可以看到当HAED指向main时, HEAD^和main^都是指向C5的.

    再次使用 git checkout HEAD~3 结果为:

撤销变更 (reset & revert)

当本地出错要回退时, 有2个命令可用.

  1. reset可将当前分支回退到指定提交. 可看执行结果:

原本提交树是这样, 使用 git reset HEAD^ 后:

可以看到, 这个命令让分支放弃了当前节点而指向了父节点.

若对原本的提交树使用 git revert HEAD^ 命令后的提交树为:

可以看到, 现在这人分支指向了C1’这个提交, 其实C1’与C1是相同的, 但是 git 将回退作为一个操作然后提交到仓库.

这样做的好处: 在本地其实是没有什么区别的, 但是如果推送到远程, 使用git reset后若要与远程同步, 需要将远程的最新提交删除, 这个操作难以实现, 因此将回退作为一个新的提交可以方便远程与本地的同步, 方便多人合作.

提交树的节点移动 (rebase & cherry-pick)

  • rebase: 它的主要作用是创建线性提交记录, 方便工程修改脉路整理. 比如对于当前提交树:

    我们使用 git rebase main后提交树变化为:

    现在分析一下这个结果. feature 和 main 分支的公共父节点是C1, 我在的HEAD指向feature, 此时运行以上命令后, git 将C2,C4 提交接到了main所指向的C3上, 也就是说: 这个命令会将当前分支的所有提交接到指定的分支上

    因此 rebase 可以表象地理解为 剪枝 + 嫁接

    如果使用merge命令的话提交树会变为:

这时提交记录则不够线性, 不方便查看对工程的修改历史

  • rebase 的另一个功能: 提交记录排序与删除.

    git rebase -i HEAD~4: 这个命令会弹出一个交互式的创口, 让你调整最新4个提交节点的顺序或者删除提交节点. 下面是一个例子:

    /ox-hugo/Xy3uNG.png /ox-hugo/HwlFsT.png

    知道这个功能即可, 不具体说明了

  • cherry-pick: 这个命令允许我们随意地移动提交树中的节点, 将指定的提交节点移动到当前的HEAD所指定的节点下.

    对以下提交树:

    使用 git cherry-pick C2 C4 C6 后结果为:

可以看到, 选中的提交节点都添加到了当前分支 main 所指向的节点下.

这样的命令有什么作用呢?

场景一: 当前出了一些问题, 可此需要新建一些分支来解决, 最终解决了当前的问题:

但是此时, 我们只想要收录解决问题的提交到当前的main下, 此时就可以使用 rebase -i 和 cherry-pick 这两个命令来实现.

将HEAD调整到main分支上, 再使用 git cherry-pick C4 (左) 或者使用 git rebase -i HEAD~3 后调整提交 (右) 即可获得:

/ox-hugo/ZJ9rxO.png /ox-hugo/n0IeTw.png

场景二: 更新历史提交节点中的信息

下面的提交树中, 我想更新newImage提交中的信息, 且是只更新它的信息, 可使用 rebase -i 和 cherry-pick 这两种方式来实现.

口述使用 rebase -i 命令过程: 先调整C2和C3顺序,将C2提交节点放到最前, 再使用 git commit --amend 来提交列新C2中的信息, 最后使用 rebase -i 命令将C3节点放到最前.

详细说明 cherry-pick 过程 (因为不用交互, 易于博客中展示). 首先调整HEAD到main: git checkout main

其次, 将C2 拿到最前: git cherry-pick C2

然后, 修改C2中的内容并提交: git commit --amend

最后, 将C3节点放到最前: git cherry-pick C3

tag

前文中提交节点要么是使用提交ID标识的, 要么是使用分支名子标识的.

分支名字会被移动, 且有新的提交时分支名字也会移动到新的提交节点中, 提交ID太长,难记.因此需要一个为某个提交节点进行永久命各的办法: 它就是tag. 对于以下的提交树:

为不同的提交节点添加tag, 如运行命令: git tag V1 C1, git tag V2 C2, 这时的提交树为:

同时, 可以直接利用tag名称来移动HEAD: git checkout V1

可以看到这时HEAD指向了C1. 为什么不是V1* 呢, 这是因为无支对某个tag直接提交, 所以才会发生这种分离HEAD的现象.

describe

git describe <ref>: <ref> 为提交树中的引用, 默认为HEAD.

该命令的输出结果为: <tag>_<num>_g<hash>, tag为与该引用最近的标签, num 为 该引用与最近标签的间隔提交数, <hash>为<ref>的提交ID.

对于以下的提交树, 使用 git describe, git describe main, git describe side 的结果分别为: