Things I Learned (Git)

Check Initial Commit🔗

Git

在阅读开源项目代码的时候,一个常见的操作是,通过查看最开始的 commits 来了解整个项目/框架最初的设计理念。对一个发展了一段时间的大型项目来说,想要直接通过 git log 找到最初的 commits 还是比较花时间的。一个取巧的办法是通过 --reverse 参数来倒序排列所有的 commits,从而快速定位最开始的那一些。同样,可以通过 -n 参数来进一步过滤展示的条数,比如通过 -n 1 来仅展示一条 commit。

最终的命令如下:

git log --reverse -n 1

Git Stash Files🔗

Git

一般情况下,使用 git stash 命令只会将当前工作区内的改动缓存起来。对于新生成的文件,默认是不会缓存的。然而在切分支的时候,难免会遇到这样的情况:当前分支的新文件在另一个分支下是已经提交的内容。此时如果切分支,可能会因为冲突而失败。如果希望将新文件也全部都缓存起来,可以两步走:

git add -A
git stash

以上两部可以合并成一步完成:git stash -u 或者 git stash --include-untracked

如果希望连那些被 git ignore 的文件也一起缓存起来,可以使用 git stash -a 或者 git stash --all 命令。当然,一般不建议这样做。比如在 JavaScript 项目中,如果使用 git stash -a 命令,很有可能会将整个 node_modules 目录都缓存起来。


How to Remove Git Merge🔗

Git

一些 Git 项目的操作规范,会禁止 Git Merge 的提交,而只允许使用 Git Rebase。以下列举一些可以去除 Git Merge 节点的操作思路。

Merge 节点在顶部

如果是最近的一个提交不小心产生了 Merge 节点,有几个方案可以参考:

  1. 可以撤销了重新用 Git Rebase 再来一次,不过坏处是可能需要重新处理一遍冲突(视情况可能会有很多次冲突处理要执行);
  2. 或者可以考虑先 git reset --soft <commit_id> 然后再重新提交,直接将所有新的改动压缩成一个 commit,不过缺点是历史 commit 信息都丢失了。

Merge 节点在中间

如果因为一些操作原因,在历史 Commit 中生成了 Merge 节点,可以考虑如下的方案去除。

首先,假设处理之前的 Git 分支如下:

A -> B -> C -> D -> M -> G -> HEAD
       \-> E -> F -/

那么,可以通过如下的命令去除 Merge 节点 M

git rebase F HEAD

这里的 F 节点是分支在 Merge 前的最后一个节点,而 Head 就是当前的最新节点。Rebase 完成之后,新的 Commit 顺序应该如下:

A -> B -> E -> F -> C -> D -> G -> HEAD

其中,A -> B -> E -> F 就是 Git Rebase 命令中到 F 点为止的整条路径,而后续的 C -> D -> G -> HEAD 就是剩下在这条路径上不存在的新节点。

可以通过如下的命令创建一个环境来手动试一试:

git init;
echo "A" > file.txt && git add -A && git commit -m "A";
echo "B" > file.txt && git add -A && git commit -m "B";
git checkout -b "new-branch";
echo "E" > file.txt && git add -A && git commit -m "E";
echo "F" > file.txt && git add -A && git commit -m "F";
git checkout -;
echo "C" > file.txt && git add -A && git commit -m "C";
echo "D" > file.txt && git add -A && git commit -m "D";
git merge new-branch;
echo "E" > file.txt && git add -A && git commit --no-edit;
echo "G" > file.txt && git add -A && git commit -m "G";

此时,只需要使用命令 git rebase new-branch HEAD 即可以去除 Merge 节点(当然,需要手动处理一些 Merge Conflict)。


Git Undo Merge Conflict Resolve🔗

Git

有时候,会遇到这样的情况:在处理 Git Merge Conflict 的时候,因为一些误判,导致选择了错误的修改方式。如果选择是在文本编辑器中完成的,那么可以通过撤销改动的方式回退到最原始的版本,重新选择;而如果文本编辑器的历史记录丢失或者改动是通过其他方式完成的,想要恢复到最开始的状态,就需要借助 Git 的 checkout -m 功能。

通过 git checkout -m 命令,可以将一个或多个指定的文件,回退到最开始的冲突状态。比如:

git checkout -m package.json

这个命令可以将 package.json 文件回退到最开始的冲突状态;而:

git checkout -m .

则可以将目录下所有的文件都回退到最开始的冲突状态(如果没有冲突的话保持不变)。

需要注意的几点:

  1. git checkout -m 在不同的情况下代表的含义是不同的。在解决冲突的时候表示重新创建冲突;在切分支的时候则等价于 --merge,表示使用 3-way merge 去合并两个分支都做了改动的文件(参考官方文档中的解释);
  2. 如果在处理冲突的时候做了其他的改动,在回退的时候不会保留这些改动:可以简单的认为,-m 做的事情就是将冲突文件还原到最初的状态。

Git Checkout Specific Side🔗

Git

在进行 Git Merge 或者 Rebase 操作的时候,经常需要处理冲突。大部分时候,解决冲突需要认真观察冲突处的代码,然后手工进行选择/修改。但是在某些情况下,解决冲突只需要简单选择某一边的修改就可以了。

举个例子:假设 1.0 版本和 1.1 版本存在并行开发。在 1.0 版本完全封板之后,可能需要将 1.0 版本的改动 rebase 到 1.1 版本提交的下面。假设两个版本都有升级版本号的提交,一个升级版本号到 1.0.1,一个升级版本号到 1.1.1。此时,进行 rebase 就会造成版本号上的冲突。在 lerna 管理的项目中,这样的冲突可能会涉及很多文件。

在已知只需要选择一边的前提下,如果还要手动解决每一个文件的冲突,显然有一些机械。Git 提供了 --ours--theirs 的 flag,可以用于快速的选择冲突的解决方案。

假设有一个冲突如下:

<<<<<<< HEAD
  "package": "1.0.1",
=======
  "package": "1.1.1",
>>>>>>> v1.1

如果希望选择当前的改动(假设文件名叫 package.json),可以使用如下的命令:

git checkout --ours package.json

如果希望选择对方版本的改动,则可以用:

git checkout --theirs package.json

更进一步,如果希望用同一套方案解决所有冲突的文件,可以直接用如下的命令(假设希望使用当前的改动):

git checkout --ours .

需要注意的一点是,在进行 Git Merge 和 Git Rebase 的时候,对应的“当前版本”(ours)与“对方版本”(theirs)的概念是不同的。对 Git Merge 来说,操作是将对方分支合并到当前的分支,因此“当前版本”(ours)就是当前分支;而对 Git Rebase 来说,操作是将当前分支的改动应用在对方分支代码的基础上,因此“当前版本”(ours)是对方分支。

抛开上面的概念,其实从 Git Conflict 的展示上看还是很直观的。上半部分(=======前的部分)对应的是“当前版本”(ours),下半部分是“对方版本”(theirs)。


Git Auto Correct🔗

Git

在 Git 中,如果输错了一个命令,Git 会给出相应的提示。比如,如果输入 git stattus,那么会有如下的输出:

git: 'stattus' is not a git command. See 'git --help'.

The most similar command is
        status

除了报错之外,Git 也给出了可能的正确答案。需要注意的是,不仅仅是 Git 自身的命令,所有配置的 Alias 也可以享受同样的待遇。比如,假设已经设置了一个 delbranch 的 Alias,那么在输入 git dlbranch 之后,也会得到如下的输出:

git: 'dlbranch' is not a git command. See 'git --help'.

The most similar command is
        delbranch

一个很直观的想法是:既然 Git 可以计算出可能的正确输入是什么,那么直接让 Git 执行那个结果,就可以避免一次重新输入了。

根据 Git 给出的文档,可以通过类似如下的配置,来打开自动纠错的功能:

[help]
    autocorrect = 30

根据 Git 文档的描述,上面提到的 30 配置,是“三秒”的意思。也就是说,配置之后,Git 会给用户三秒的时间反悔,否则就会执行(可以通过 Ctrl+C 阻止纠错被自动执行)。输出如下:

WARNING: You called a Git command named 'stattus', which does not exist.
Continuing in 3.0 seconds, assuming that you meant 'status'.

另,The Fuck 也是一个类似思路的 Bash 自动纠错解决方案,避免重复输入。


Git Force Push with Lease🔗

Git

在一些 Git 开发流程中,需要使用 Git Rebase 来确保分支同步(比如在某一个 feature 分支上开发代码,通过 Rebase 来保证分支上一直可以有最新的开发分支上的所有提交内容)。这种情况下,在 Rebase 完 master(或其他代码提交分支)的代码之后,往往需要通过 Force Push 的方式,将 Rebase 的结果覆盖远程的工作分支。然而,Git Force Push 是一个有潜在危险的工作:当一个人在进行 Force Push 操作的时候,如果正好有另一个开发者在远端提交了新的代码,Force Push 会将远端他人的改动直接覆盖(删除)掉,导致一些代码提交丢失。

造成这一问题的直接原因,是 Force Push 的时候,代码没有带上最新的远端改动。然而,要解决这一问题却不是非常容易。因为无论进行 Rebase 的开发者如何小心,在进行网络操作进行提交的过程中,都有可能因为潜在的时间差,导致覆盖提交。

Git 为此设计了一个新的 API --force-with-lease 来解决这一问题。解决的思路是这样的:

当使用 --force-with-lease Flag 进行提交的时候,Git 会将当前提交者本地远程分支内的提交和真正远程服务器上的提交进行比较。如果两者是相同的,那么就会允许这一次的 Force Push 操作;如果发现是不同的,那么很大概率就是远端有了他人的新提交,这时 Force Push 就不会成功了。

此时,提交者应该通过 git fetch 的方式拿到最新的代码,确认是否需要进行更新改动,然后再次提交。

需要注意的是,因为 Git 只是进行了本地远程分支和远程分支的比较,因此 git fetch 之后即使什么也不做,直接再一次进行 git push --force-with-lease 操作也是可以成功的。所以操作的安全性最终还是需要人来保证,Git 只是提供了工具以确保人不会在无意间犯错误。


Git Branch Sort by Latest Commit🔗

Git

在一个大型项目中,有多个分支并行处理需求、线上问题修复是很常见的事情。然而,分支一旦多了,就不好管理了。

常规情况下,如果直接通过 git branch 命令将所有分支列出来,可能会是一个长长的列表,一下子也找不到重点。

在 Git 中,可以通过以下的方法将分支按最后提交 Commit 的日期进行排序:

git branch --sort=committerdate   # ASC
git branch --sort=-committerdate  # DESC

其中,用 -committerdate 排序时,最新的会出现在列表的最上面;而使用 committerdate 排序时,最新的会出现在列表的最下面。如果本地的开发分支非常多,terminal 一屏展示不下,可以使用 --sort=committerdate 将最新的放到最下面,方便查看。


Git Diff Filenames🔗

Git

~/.gitconfig 中进行如下配置(或者使用命令:git config --global diff.noprefix true):

[diff]
    noprefix = true

之后,Git 输出的 diff 内容,比较的文件名前将不再包含 a/b/ 这样的前缀。

举例来说,在配置前,使用 git diff 命令,看到的输出可能如下:

diff --git a/package.json b/package.json
index ac6f0b2..f937b7b 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
   "bugs": {
     "url": "https://github.com/laysent/some-codemod/issues"
   },
-  "version": "0.1.2",
+  "version": "0.2.0",
   "license": "MIT",
   "scripts": {
     "test": "jest"

而进行了配置之后,输出如下:

diff --git package.json package.jsonindex ac6f0b2..f937b7b 100644
--- package.json
+++ package.json
@@ -13,7 +13,7 @@
   "bugs": {
     "url": "https://github.com/laysent/some-codemod/issues"
   },
-  "version": "0.1.2",
+  "version": "0.2.0",
   "license": "MIT",
   "scripts": {
     "test": "jest"

此时,无论是直接在终端复制这个文件名,还是直接点击文件名跳转打开,都比较容易。

(来源:tweet from @brandur

需要注意的一点是,如果配置了 noprefix,那么在进行 git diff 创建 Patch 文件并通过 git apply 提交修改的时候,可能会遇到 Git 的报错:

error: git diff header lacks filename information when removing 1 leading pathname component (line 5)

原因就是生成的 Patch 文件,目录名称没有了前缀。针对这种情况,可以改用下面的方案进行 git apply

git apply -p0 change.patch

这里,-p0 表示 Git 在进行补丁操作的时候,需要先删除层前缀字符,然后再读取真实的目录地址。这里,Git 会根据 / 字符将目录地址拆分开来,然后删除必要的层数,将剩下的部分作为文件地址。默认值是 1,也就是会将 a/package.json 当作 package.json 目录进行处理。如果改成 -p2,那么 a/dir/file 会被当成 file 目录进行处理。

相关的说明可以参考 Git 文档


Apply Git Patch🔗

Git

在实际开发过程中,可能会遇到这样的问题:因为重构,一些文件从 A 目录移动到了 B 目录,而后又对文件内容做了修改。这时,如果希望将其中的某些修改(比如和安全相关的布丁)应用回重构前的代码,就显得比较困难了。直接通过 Git 进行 cherry-pick 并不顺利,因为具体修改的 commit 中并不包含文件目录移动的信息。

可以简单使用下面的命令来构建一个场景:

git init;
echo "console.log('hello world');" > origin.js;
git add -A;
git commit -m "first commit";

git checkout -b "new_branch";
mv origin.js modified.js;
git add -A;
git commit -m "rename commit";

echo "console.log('hi')" >> origin.js;
git add -A;
git commit -m "modify commit";

git checkout master;

这时候,希望直接将 new_branch 中最后一个 commit cherry-pick 到 master 是比较困难的。

针对这种情况,可以考虑使用 Git Patch 功能。首先将修改的部分生成 Patch 文件,然后手动将 Patch 中的目录映射关系处理正确,最终将修改后的 Patch 应用到重构前的某个旧版本中。

创建 Patch

git diff 命令输出的结果就是一个 Patch,可以简单的将输出的内容存储到文件中,就生成了一个当前未签入内容的 Patch 文件:

git diff > change.patch

如果希望只是将部分修改的文件生成 Patch,可以先将需要的部分放入缓冲区中(git add),然后通过 git diff --cached 命令,仅针对缓冲区中的修改生成 Patch 文件。

以上这些生成的方案,比较适合为没有写权限的 Git 仓库提交修改的场景。直接将 Patch 文件通过 email 的形式发送,就可以进行修改的讨论了。

注:如果改动包含了二进制文件的修改,可以通过增加 --binary 命令来获取到这部分文件的修改 Patch。

针对已经签入的提交,也可以通过 git format-patchgit show 命令来生成 commit 对应的 Patch 文件。

git show commit-id > change.patch

可以生成单个 commit 的 Patch 文件;如果希望生成一组 commit 的 Patch,可以使用:

git format-patch A..B

上面的命令会生成为从 A 到 B 之间的所有 commit 生成对应的 Patch 文件(包含 B commit,但是不包含 A commit;如果需要包含 A,可以使用 A^..B 命令)。或者,如果希望将所有的改动合成到一个 Patch 文件中,可以使用:

git format-patch A..B --stdout > changes.patch

上面的 A 和 B 除了可以是 commit id 之外,也可以是 Branch 或者 Tag。

应用 Patch

将生成的 Patch 文件应用到当前的代码中,只需要使用:

git apply change.patch

Git 会将 Patch 中提到的修改应用到当前的项目中,但改动不会被自动提交;如果希望直接将 Patch 以 commit 的形式进行提交,可以直接使用:

git am change.patch

关于 Patch

Git 生成的 Patch 文件,除了提交作者、commit message 这些信息外,核心的部分是通过 diff 命令生成的修改内容。如果只是需要修改一下文件的位置,应该可以通过观察文件直接找到。更多关于 diff 命令生成的补丁文件的格式,可以参考 Wikipedia 中的相关描述。


Cherry-pick Range of Git Commits🔗

Git

在 Git 中,可以通过 cherry-pick 命令将某一个 commit 选到当前的分支上。在 Git 1.7.2+ 中,可以支持将一组连续的 commit 全部都选到当前的分支上。

使用的语法是:

git cherry-pick A..B

或者

git cherry-pick A^..B

这里,A..B 要求 A 是在 B 的前面(更老)。在实际挑选的过程中,A 并不会被选入,实际选入的是 A 之后的下一个 commit,直到 B 为止。如果希望选择也包括 A 这个提交,可以使用 A^..B 的语法。


Checkout Previous Branch🔗

Git

在 Git 中,可以通过 git checkout - 切换会上一个分支。重复使用该命令,就会在最近的两个切换的分支上往复。

需要注意的一点是,虽然 git worktree 之间是共用同一个 .git 数据的,但是切换的分支也是当前目录下最新使用的两个分支。其他 worktree 上的分支切换记录不会影响到当前目录的切换行为。


Git Worktree🔗

Git

在实际的开发过程中,经常有多分支并发操作的情况,比如:

  1. PC 软件需要维护多个版本,在新的版本分支上开发新功能,同时维护旧的版本以修复问题;
  2. 针对 Gerrit 这类只允许单 commit 迁入的工具,一个版本开发多个功能,可能需要分成多个分支同时进行

这种情况下,在版本间切换往往有两个常见的方式:

  1. 将当前的代码 stash 后,切换分支,进行对应的处理,处理完了再回到原来的分支 git stash pop 继续原先的开发工作;
  2. 直接 git clone 一个新的仓库,在上面完成必要的工作

第一种方案的问题主要是,切换多次的话,很容易搞不清楚当前分支下还有哪些是 stash 的,管理起来有点麻烦。有时候方便起见,也会直接将当前的内容 commit 到分支上,再进行切换。考虑到 git hook 的存在,commit 可能还需要加上 --no-verify

git add -A && git commit -m "wip" --no-verify

第二种方案的问题主要是,多个文件仓库重复下载了多次 .git 目录,在一些大型项目中,这里会导致大量的硬盘空间被浪费。

Git 在 2.5 版本中提供了 worktree 的功能,用于解决这一痛点。在一个 Git 项目中,只需要执行如下的命令,就可以新创建一个文件仓库:

git worktree add -b new-branch-name /path/to/folder origin/branch/name

新创建的文件仓库被放在 /path/to/folder 中,使用的仓库名称是 new-branch-name,基于 origin 上的 branch/name。如果只需要使用一个已经存在的分支,可以简化成:

git worktree add /path/to/folder local/branch/name

之后,在 /path/to/folder 中就可以进行常规的开发了。值得一提的是,原 Git 目录下的 Hook 文件也会一并同步到新的工作目录下,可以直接使用。通过查看目录下的文件,不难发现 Git 的同步方式。事实上,在 WorkTree 目录下,并没有一个 .git 目录,取而代之的,只有一个 .git 文件,里面标注了真正的 .git 目录应该去那里查找。比如:

gitdir: /path/to/actual/.git/worktrees/name

也正因为如此,WorkTree 下所有的 Git 配置都是同步的。

如果需要查看当前的 Git 中到底有多少个 WorkTree,可以使用下面的命令:

git worktree list

命令会列出所有 WorkTree 的目录以及当前使用的分支名称。

在开发完成后,如果希望删除 WorkTree,可以使用下面的命令:

git worktree remove /path/to/folder

删除完成后,可以通过 git worktree list 来检查是否真的被删除了。

延伸阅读:


Git Pull Rebase🔗

Git

默认情况下,使用 git pull 拉取最新代码的时候,Git 会触发 git merge 来进行远端代码和本地代码的合并。如果两份代码之间没有冲突,那么 Merge 行为可以进行 Fast Forward,最终的结果是比较“干净”的 Commit;然而,如果 Fast Forward 无法进行,那么最终的显示效果,是 git 的历史中会多出一条 Merge 的 commit。

在绝大多数情况下,这类 Merge commit 都是多余的。这种时候,一般会建议使用 git pull --rebase 命令来拉取代码。这样,拿到最新代码后,Git 会使用 rebase 而不是 merge 来进行远端代码和本地代码的合并(关于 Merge 和 Rebase 的一些讨论,可以参考 Atlassian 的文章)。

当然,每次都这么写会比较繁琐。一个简单的方法,是通过 Shell 进行下面的 Git 配置:

git config --global pull.rebase true

或者,等价的,可以在 ~/.gitconfig 文件中,增加如下的配置信息:

[pull]
  rebase = true

(针对 Git 版本小于 1.7.9 的情况,配置可以参考这里

如此一来,git pull 的默认行为就会从 merge 变成 rebase。

在这种情况下,如果希望使用 merge 的行为,可以写 git pull --no-rebase


Get Branch Name & Commit ID without Git🔗

Git

可以使用 Git 命令行工具获取到当前使用的分支名称,最新的 Git Commit ID 等信息。然而,在不借助 Git 命令的情况下,依然可以通过 .git 文件,找到这些信息。

在 .git 文件夹中,HEAD 文件记录了当前分支的指向。文件内容 refs 后面跟着的就是分支名。这个分支名亦是一个路径,在 .git 目录下使用这个相对路径可以得到当前分支指向的头部 Commit ID。

举个例子:

发现 ./.git/HEAD 中的内容是:ref: refs/heads/master,通过查看 ./.git/refs/heads/master 文件中的内容,就可以知道当前的头部 Commit ID。同样,去除 refs/heads 之后,就可以得到当前的分支名称,即 master

对应的 Node.js 代码如下:

const git = path.resolve(process.cwd(), '.git');
const head = path.resolve(git, 'HEAD');
const ref = fs.readFileSync(head, 'utf8').trim().substr('ref: '.length);
const commit = fs.readFileSync(path.resolve(git, ref), 'utf8').trim();
const branch = ref.substr('refs/heads/'.length);

console.log(branch, commit);

Abort Git Rebase Process🔗

Git

在 Git 的一些操作中,可能会中途停下来,等待用户输入的操作。比如,git rebase -igit ammend 的时候。在完成操作前,Git 会打开 Vim(或其他默认的编辑器)等待用户对 commit message 做最后的处理。只要用户保存并退出,rebase 的过程就完成了。

如果在这个等待确认的过程中,希望可以中断整个过程,使用 Ctrl+C 是不行的。如果使用 Ctrl+C,Vim 可能会提示用 qa! 来放弃所有修改并退出 Vim。但这个只是退出了 Vim,Git 依然会继续接下来的流程,并没有真正达到中断 Git 的目的。

事实上,Vim 允许以 error code 退出,使用如下的命令::cq

更多关于这个命令的说明,可以使用 :help cq 来查看。以 error code 退出之后,Git 就不会再继续接下来的流程了。


git rev-parse🔗

Git

rev-parse 并不是 Git 中一个不常用的命令。Git 的一些命令底层会使用 rev-parse 来处理输入的参数。

通过 rev-parse 可以获得一些有用的 Git 数据,比如:

  • 获取当前的 commit id
git rev-parse HEAD
  • 获取当前的分支名
git rev-parse --symbolic-full-name --abbrev-ref HEAD

Multiple Git Configuration🔗

Git

对于有多个 Git 仓库的情况,不同的仓库可能需要配置不同的用户信息。

一种麻烦的方案是,每个仓库都配置一个本地的 Git 配置,不使用全局的设置,就不会有问题。但是这样配置非常的麻烦,也容易忘。Git 提供了配置覆盖的功能,可以指定某一子目录,使用另外一个指定的 Git 配置覆盖默认的全剧配置。

如下:

[includeIf "gitdir:~/work/github/"]
    path = ~/work/github/.gitconfig

这个配置指定了在 ~/work/github/ 目录下,除了全局的 .gitconfig 文件之外,读取 ~/work/github/.gitconfig 文件对配置进行覆盖改写。在 ~/work/github/.gitconfig 的优先级高于 ~/.gitconfig 的配置,会优先使用,没有定义的部分才会去全局中找。


Command to enter folder after git clone🔗

Git

下面的脚本,执行之后,可以完成 git clonecd 至目标文件夹内。

!f() {
  local tmp=$(mktemp);
  local repo_name;
  git clone $@ --progress 2>&1 | tee $tmp;
  repo_name=$(awk -F\' '/Cloning into/ {print $2}' $tmp);
  rm $tmp;
  cd $repo_name;
};
f

一些说明:

  • mktemp 可以创建一个临时文件,文件路径存放在 tmp 变量中
  • git clone $@ 中的 $@ 是执行脚本时候所有的传入参数
  • ---progress 2>&1 会将 Git clone 命令的结果输出。这里,默认情况下,clone 的过程数据只有在 error stream 输出到 terminal 的时候,才会显示。因为这里的命令需要将内容通过管道输出到 tmp 临时文件中,所以默认情况下 Git 就不会输出过程的数据了。为了能够让 Git 输出这部分内容,需要加上 --progress
  • tee $tmp 将管道的数据输出到临时文件中
  • awk -F\' '/Cloning into/ {print $2}' $tmp 的部分,会从输出的数据中,寻找 Cloning into 的输出,然后找到具体 clone 到了哪个文件夹中

Deletion of file in git🔗

Git

假设发现一个文件在历史版本中存在,但是当前不存在了,那么可能就需要知道是在什么时候,因为什么原因对文件做了删除。下面的命令可以一次性找出某一个文件的所有记录:

git log --full-history -- [file path]

如果只需要看最后一条记录(也就是被删除的那条记录),可以用:

git log --full-history -1 -- [file path]