Git次常用命令小抄 | 青训营

283 阅读5分钟

Git是一个开源的分布式代码管理工具,是项目版本控制的必备工具。由于大部分人都很熟悉常用的命令如pull, push, merge,笔者特意选取一些稍微“小众”的命令,并结合他们对应的应用场景进行讲解。读者很快将看到这些次常用命令也会在特定情况下变得很有用。

下面的表格总结了本文中出现的场景以及对应的Git命令。

场景命令说明
工作区有未提交的改动,但是想在不撤销这些改动的情况下暂时离开当前分支git stash, git stash apply等到切换回先前的分支后跑git stash apply
想查询某次提交中改动过的文件及改动操作的类型,而不太在意改动的细节git show --name-status <commit id><commit id>是一个或多个想要查看的提交id
想将文件从Git版本控制中移除,但是又想在工作区中保留该文件git rm --cached <path to file><path to file>是文件的路径
想要将项目的版本回滚到某一次提交时的状态git reset <option> <version><version>代表要回滚到的版本,<option>一般有三种值: --soft, --mixed, --hard

Git stash

stash可以用于贮藏自己在工作区已做的改动。如果已经在一个分支上做了改动但是想在不提交的状态下切换到另一个分支,则可以用git stash push或者git stash(省略子命令push)来将现在的工作区改动压入一个栈中,而当稍后返回这个分支时可以使用git stash apply来还原自己在工作区做的改动。

下面是一个演示git stash的简单例子。首先,我们在分支A的提交1的基础上工作,对文件做了一些修改,这时如果因为有急事需要离开当前的提交,访问分支B上的提交1,但是又不想放弃自己暂未提交的改动,我们可以执行第一步:git stash,将工作区改动压入stash栈顶(stash @{0})。第二步,用git checkout B离开分支A。第三步,当我们结束在分支B的访问返回分支A,git checkout A。第四步,执行git stash apply将stash栈顶的改动应用到当前的分支A中。这之后我们在工作区的状态就与离开分支A之前一模一样。

---
title: git stash 
---
flowchart LR
    b1((分支A)) --- c1[提交1] -.-> |第一步| s1[("stash栈 @{0}")]
    b2((分支B)) --- c3[提交1]
    c1 --> |第二步| c3
    c3 --> |第三步| c1
    s1 -.-> |第四步| c1

Git show --name-status

有时候我们需要查询某一次提交中修改过的文件,但是不需要像git diff那样详细地把每一处更改都列出来,这个时候我们可以使用git show命令加上一些指示性的选项。例如git show --name-status指示Git只打印出每个文件更改的类型,如"D"代表删除,"M"代表修改, "A"代表添加, "R"代表重命名或移动。需要注意的是当想查看除了最近一次之外的提交时需要在git show命令后跟上提交的id。如下面这个例子以文件+改动的简短形式查看了两次提交305bb5a, 6a47458的更改。

~/git-demo$ git show --name-status 305bb5a  6a47458
commit 305bb5aea12a51958872da859883486874795253
Author: for shell <fromShell@paas.com>
Date:   Wed Aug 23 22:51:40 2023 +0800

    commit 2

D       file1

commit 6a47458b17523889f345f556135b431a96082f42
Author: for shell <fromShell@paas.com>
Date:   Wed Aug 23 22:16:04 2023 +0800

    commit 1

A       file1
A       file2

Git rm --cached

git rm本身是一个删除索引中文件的动作。但是当加上--cached选项之后它就变成从git的索引中删除文件。在Git的官方手册中,对--cached选项是这么描述的:“使用该选项仅会从索引(index)中移除路径对应的文件,但是不会对工作区中的文件产生改动”。由此可见这个命令只会将删除某个文件的改动记录到git版本库中,但是仍会在工作区中保留这个文件。下面是一个该命令非常有用的情况。

假设我们在某一次的提交中添加了文件file1,但是在接下来的提交中我们想取消git对文件file1改动的记录,从而使下一次的提交中不会包含file1。则我们可以使用git rm --cached /path/to/file1在下一次提交前将file1从索引中移除,其中/path/to/file1是可以访问到file1的路径。

  1. 当前工作的分支是A,提交是commit 1。
~/git-demo$ git branch -v
* A 6a47458 commit 1
  1. 查看commit 1中记录的两个文件,可以用上一小节提到的命令git show --name-status,我们看到commit 1中记录着file1, file2的添加。
~/git-demo$ git show --name-status
commit 6a47458b17523889f345f556135b431a96082f42 (HEAD -> A)
Author: for shell <fromShell@paas.com>
Date:   Wed Aug 23 22:16:04 2023 +0800

    commit 1

A       file1
A       file2
  1. 在commit 1的基础上,如果我们不想让file1出现在下一次的commit中,换句话说,要使Git停止追踪file1,使用git rm --cached file1
~/git-demo$ git rm --cached file1
rm 'file1'
  1. 这时使用git status -s可以清楚地看见,Git记录了file1文件从索引的删除,但同时工作区的file1文件仍被保留着。
~/git-demo$ git status -s
D  file1
?? file1
  1. 这时如果将改动提交到commit 2,再使用git status -s,就会发现文件file1已经变成了未被索引的状态。
~/git-demo$ git commit -m "commit 2"
[A 305bb5a] commit 2
 1 file changed, 1 deletion(-)
 delete mode 100644 file1
~/git-demo$ git status -s
?? file1

Git reset

reset命令一般用于回滚到先前的代码版本。这个命令使用的基本语法是git reset <option> <version>,其中<version>代表要回滚到的版本,可以直接用提交id表示,也可以用HEAD(最新的版本),HEAD~(倒数第二新的版本),HEAD~~(倒数第三新的版本)的方法按时间顺序指定目标版本。<option>reset命令的选项,一般来说有三种情况:

  1. --soft:仅把版本库中的最新版本(HEAD)回退到指定的版本,但是回退前的索引中的文件不会改变,而且被回退覆盖的所有历史提交中设计的改动都会转成工作区中未被提交的改动。
  2. --mixed:不仅把版本库中的最新版本回退到指定版本,而且还用版本库中的最新版本替代索引中的所有文件。结果是索引中没有可以提交的更改。换句话说--mixed就是在--soft的更改上把所有索引区的更改消除了。
  3. --hard:不仅把版本库中的最新版本回退到指定版本,而且还用回退到的版本覆盖索引区和工作区的所有文件。--hard是做得比--mixed更深一层,因为工作区中的所有文件都被回退到的版本覆盖了,这也是最危险的情况,因为工作区所有未提交的改动都会丢失。

由此可见,这三个选项按回退的程度分级是由浅到深。每一个选项都是在前一个选项的基础上扩大了回退的作用区域。关于这三个选项的图解,Git社区的官方指南里面有一个很清晰易懂的解释。

参考文档

  1. Scott Chacon, Jason Long, Git community. "7.3 Git 工具 - 贮藏与清理". git-scm.com/book/zh/v2/…
  2. 菜鸟教程. "Git 工作区、暂存区和版本库". www.runoob.com/git/git-wor…
  3. Scott Chacon, Jason Long, Git community. "7.7 Git 工具 - 重置揭密". git-scm.com/book/en/v2/…