Git技法:.gitignore、移除暂存与撤销修改

1. .gitignore 常见项目添加

1.1 .gitignore 模板

.gitignore针对每个语言都有对应的模板,在GitHub创建项目时就可以选择(你可以在GitHub提供的.gitignore模板大全中找到它)。如Python语言的. gitignore模板如下:

Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

C extensions
*.so

Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

PyInstaller
 Usually these files are written by a python script from a template
 before PyInstaller builds the exe, so as to inject date/other infos into it.

*.manifest
*.spec

Installer logs
pip-log.txt
pip-delete-this-directory.txt

Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

Translations
*.mo
*.pot

Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

Flask stuff:
instance/
.webassets-cache

Scrapy stuff:
.scrapy

Sphinx documentation
docs/_build/

PyBuilder
target/

Jupyter Notebook
.ipynb_checkpoints

IPython
profile_default/
ipython_config.py

pyenv
.python-version

pipenv
  According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.

  However, in case of collaboration, if having platform-specific dependencies or dependencies
  having no cross-platform support, pipenv may install dependencies that don't work, or not
  install all needed dependencies.

#Pipfile.lock

PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

Celery stuff
celerybeat-schedule
celerybeat.pid

SageMath parsed files
*.sage.py

Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

Spyder project settings
.spyderproject
.spyproject

Rope project settings
.ropeproject

mkdocs documentation
/site

mypy
.mypy_cache/
.dmypy.json
dmypy.json

Pyre type checker
.pyre/

1.2 添加更多的 .gitignore 项目

但是这些往往是不够的的。如我们在Mac系统下用VSCode开发,那么常常还需要添加以下项目:

IDE - VSCode
.vscode/

OS generated files
.DS_Store

其中 .vscode/表示忽略 .vscode这个包含项目配置文件的隐藏目录(注意是包括目录一起忽略,这个和Linux下诸如 cp test/ .这类命令的语义有区别,参加我的博客《Linux:文件解压、复制和移动的若干坑》), .DS_Store表示忽略掉Mac操作系统下存储目录自定义属性的隐藏文件。

此外,我们再以机器学习相关的项目为例子,数据(放在 data目录下)和模型(放在 model目录下)通常异常巨大,我们并不想将它们放到项目文件夹下,因此我们可能倾向于添加如下的项目:

data files
data/*

model files
model/*

data/*model/*语义上表示忽视 data目录下所有文件与 model目录下所有文件及子目录(不包括 datamodel 目录本身)。但是我们会发现, 实际上空的 datamodel 目录并没有成功 git add 到项目中

(base) orion-orion@MacBook-Pro Learn-Git % git add data
(base) orion-orion@MacBook-Pro Learn-Git % git add model
(base) orion-orion@MacBook-Pro Learn-Git % git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.

  (use "git push" to publish your local commits)

nothing to commit, working tree clean

这是因为空目录不会称为Git版本控制系统跟踪(track)。但是如果我们想保存 datamodel的目录架构呢?很简单,我们只需要在 datamodel目录下添加 .gitkeep目录即可,然后将在 .gitignore文件中对 .gitkeep进行反选(即不忽视):

data files
data/*
!data/.gitkeep

model files
model/*
!model/.gitkeep

可以看到由于隐藏文件的存在,现在空目录能够正常 git add了:

(base) orion-orion@MacBook-Pro Learn-Git % git add data
(base) orion-orion@MacBook-Pro Learn-Git % git add model
(base) orion-orion@MacBook-Pro Learn-Git % git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.

  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged ..." to unstage)
        new file:   data/.gitkeep
        new file:   model/.gitkeep

[TencentCloudSDKException] code:FailedOperation.ServiceIsolate message:service is stopped due to arrears, please recharge your account in Tencent Cloud requestId:0ef26d3b-58f3-4243-b3ec-89256d022e43

[En]

[TencentCloudSDKException] code:FailedOperation.ServiceIsolate message:service is stopped due to arrears, please recharge your account in Tencent Cloud requestId:18608ec0-257d-4290-a6fe-5f4f6364a053

data files
data/
!data/.gitkeep

因为 data/表示将data目录本身也忽略了,Git根本就不会去查看该目录,以致 .gitkeep文件也就不起作用了。

额外提一下,如果我们仅仅希望忽略掉 data目录下的 .csv文件,可以这样写:

data files
data/*.csv

2. 移除已暂存(staged)的文件

2.1 关于跟踪与暂存

在Git中,一个文件可能在这三种区域中:工作目录(Working Directory),暂存区(Staging Area,也称索引index),Git仓库(可视为一棵提交树committed tree)。三者关系如下图所示:

Git技法:.gitignore、移除暂存与撤销修改

[TencentCloudSDKException] code:FailedOperation.ServiceIsolate message:service is stopped due to arrears, please recharge your account in Tencent Cloud requestId:7bb36aab-fdfc-4438-9c8b-a05323d24e38

[En]

[TencentCloudSDKException] code:FailedOperation.ServiceIsolate message:service is stopped due to arrears, please recharge your account in Tencent Cloud requestId:fdccd977-ab0c-4c5d-bdd2-6000afda1718

一旦一个目录或文件被 git add了一次,那么它就会被跟踪(track)并加入暂存区。此后再对其进行修改,Git会提醒你 Changes not staged for commitmodified: README.md,需要再次运行 git add将其暂存(staged):

(base) orion-orion@MacBook-Pro Learn-Git % echo "new version" > README.md
(base) orion-orion@MacBook-Pro Learn-Git % git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.

  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
</file></file>

而文件的所谓的未跟踪(untracked)、未修改(unmodified)、已修改(modified)、已暂存(staged)四种状态的关系如下所示:

Git技法:.gitignore、移除暂存与撤销修改

2.2 清除已暂存的文件

现在假设我们搞忘了编写 .gitignore,然后已经用了 git add -Agit add .命令目录下所有文件及子目录都暂存了(在Git 2.0中 git add -Agit add .命令等效)。而其中有很大的日志文件或一些诸如 *.a的编译文件,我们如何将这些文件从暂存区域移除以取消跟踪呢?可以用 git rm --cached命令完成此项工作,如:

git rm --cached README.md

注意要带上选项 --cached,而不仅仅是 git rmgit rm除了从暂存区域移除外,还会将磁盘上的文件也一起删了。关于参数选项可以参见我的博客《Linux:可执行程序的Shell传参格式规范 》

使用该命令效果如下:

(base) orion-orion@MacBook-Pro Learn-Git % git rm --cached README.md
rm 'README.md'
(base) orion-orion@MacBook-Pro Learn-Git % git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.

  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    README.md
</file>

注意到 Changes to be committed:deleted: README.md,这说明当我们使用 git rm --cached并commit后, 相关的文件还会被从committed tree中移除。如果我们只想移除出暂存区,可以使用下列命令:

 git reset HEAD README.md

该命令等同 git reset --mixed HEAD README.md(默认参数为 --mixed,它还可选的参数包括 soft--hard,我们放在 3.3节讲)。使用后效果如下:

(base) orion-orion@MacBook-Pro Learn-Git % git reset HEAD *.md
Unstaged changes after reset:
M       README.md
(base) orion-orion@MacBook-Pro Learn-Git % git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.

  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
</file></file>

注意到 Changes not staged for commit:modified: README.md。说明该命令只是将 README.md移除暂存区,但是上次对 README.md的commit还在(即撤销最近的一次commit之后的变化)。

如果要递归地将当前目录下的所有文件及子目录移除出暂存区(与commit tree),可以这样写:

git rm -r --cached .&#xA0;

[TencentCloudSDKException] code:FailedOperation.ServiceIsolate message:service is stopped due to arrears, please recharge your account in Tencent Cloud requestId:3784c60a-4403-4ac8-afa0-7056b1cededf

[En]

[TencentCloudSDKException] code:FailedOperation.ServiceIsolate message:service is stopped due to arrears, please recharge your account in Tencent Cloud requestId:14abdf06-ca5e-4523-b5ee-757432e2a7e3

3. 追加与撤销 git commit 操作

3.1 commit历史查看

git log命令可以看到项目的 git commit历史:

(base) orion-orion@MacBook-Pro Learn-Git % git log
commit 37a35d36eaf8b56c9e7b719c3c7576f3251cee36 (HEAD -> main)
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon May 23 14:15:21 2022 +0800

    modify .gitignore

commit ab7bf6e2c400c8d775cc3bc56928c7748c63c8f8
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon May 23 10:08:08 2022 +0800

    add .gitignore

commit 146c68e12fd2aebed8b38dd5cf95621f800fe4aa (origin/main, origin/HEAD)
Author: &#x730E;&#x6237;&#x5EA7; <46917784+orion-orion@users.noreply.github.com>
Date:   Sun May 22 09:48:22 2022 +0800

    Initial commit
</46917784+orion-orion@users.noreply.github.com></orion-orion@foxmail.com></orion-orion@foxmail.com>

默认不用任何参数的话, git log会按提交时间列出所有的更新,最近的更新排在最上面。 正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址(如果电子邮件名为 <46917784+orion-orion@users.noreply.github.com><!--46917784+orion-orion@users.noreply.github.com-->,说明你在GitHub中将邮件名设置为私有的了,需要去修改一下)、提交时间以及提交说明。

3.2 追加commit操作/修改commit信息

现在我们又对 .gitignore进行了修改。但是我们不想又commit一次,而想将其合并在最后一次的 modify .gitignore里,使commit记录更为精简。我们可以用以下命令:

(base) orion-orion@MacBook-Pro Learn-Git % git add .gitignore
(base) orion-orion@MacBook-Pro Learn-Git % git commit --amend

并在commit信息的编辑界面写入 modify .gitignore(如果要修改commit信息,此处改成其它的即可):

modify .gitignore

Please enter the commit message for your changes. Lines starting
with '#' will be ignored, and an empty message aborts the commit.

#
Date:      Mon May 23 14:15:21 2022 +0800
#
On branch main
Your branch is ahead of 'origin/main' by 2 commits.

  (use "git push" to publish your local commits)
#
Changes to be committed:
      modified:   .gitignore
      new file:   data/.gitkeep
      new file:   model/.gitkeep
#
Changes not staged for commit:
      modified:   README.md
#

:wq!

可以看到总的commit记录没变,所显示的最后一次commit记录的时间也没变,但新的修改已经追加进去了(SHA-1 校验和发生了变化):

(base) orion-orion@MacBook-Pro Learn-Git % git log
commit a0dfeff409494165bdff60c27b24fad2bc0ed0ad (HEAD -> main)
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon May 23 14:15:21 2022 +0800

    modify .gitignore

commit ab7bf6e2c400c8d775cc3bc56928c7748c63c8f8
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon May 23 10:08:08 2022 +0800

    add .gitignore

commit 146c68e12fd2aebed8b38dd5cf95621f800fe4aa (origin/main, origin/HEAD)
Author: &#x730E;&#x6237;&#x5EA7; <46917784+orion-orion@users.noreply.github.com>
Date:   Sun May 22 09:48:22 2022 +0800

    Initial commit
</46917784+orion-orion@users.noreply.github.com></orion-orion@foxmail.com></orion-orion@foxmail.com>

我们在暂存区没有任何更新的情况下,也可以使用 git commit --amend命令来单纯修改commit操作的附加信息。
但是上面的操作只能修改最近一次的commit信息,如果我们要修改几步之前的commit信息呢?这时就要用到 git rebase操作了。
比如如果我们用 git log查看到以下修改记录:

(base) orion-orion@MacBook-Pro Learn-Git % git log
commit 34381d5208c81b2a9846402225cd9a559163c2ef (HEAD -> master
)
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:43 2022 +0800

    add file4

commit 12eb0b1bdbdc41a8d7b1b63ed777135de2396e25
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:27 2022 +0800

    add file3

commit 123ac795a8a283c89737eeb83d2a62210c55cc0c
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:11 2022 +0800

    add file*

commit b04ef5bbf5bf70296790c7e9fbe71b27afd04a3f
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:38:46 2022 +0800

    add file1
</orion-orion@foxmail.com></orion-orion@foxmail.com></orion-orion@foxmail.com></orion-orion@foxmail.com>

现在我们觉得导数第3个commit记录不太和谐,想把它修改为 add file2。那么我们需要使用 git rebase -i HEAD~3。注意:这里 HEAD~0为当前工作的分支, HEAD/ HEAD~1为上次提交发生之前的工作分支, HEAD~3为上上上次提交发生之前的工作分支,此处 git rebase -i HEAD~3即为将 HEAD指针指向上上上次提交发生之前的工作分支。

PS: 在Git中分支就是指向提交对象的可变指针

然后显示

pick 123ac79 add file*
pick 12eb0b1 add file3
pick 34381d5 add file4

需要在编辑模式下将第一行的 pick修改为 edit

edit 123ac79 add file*
pick 12eb0b1 add file3
pick 34381d5 add file4

然后在底行模式输入 :wq保存退出。最后再和上面一样输入 git commit --amend(如果还需要追加文件,则在这之前还需要使用 git add命令将文件添加到暂存区),显示:

add file*

Please enter the commit message for your changes. Lines starting
with '#' will be ignored, and an empty message aborts the commit.

...

需要再编辑模式下将 add file*修改为 add file2

add file2

Please enter the commit message for your changes. Lines starting
with '#' will be ignored, and an empty message aborts the commit.

...

此时输入 git log,可以看到已经修改完毕,不过 HEAD指针是指向 add file2对应的commit对象的:

(base) orion-orion@MacBook-Pro Learn-Git % git log
commit 19f39c0883a0b5b9e7ebcef4e9efd82c72e8bb44 (HEAD)
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:11 2022 +0800

    add file2

commit b04ef5bbf5bf70296790c7e9fbe71b27afd04a3f
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:38:46 2022 +0800

    add file1
</orion-orion@foxmail.com></orion-orion@foxmail.com>

为了回到我们原来的 master分支,我们需要运行 git rebase --continue命令。运行完毕后。再使用 git log查看commit记录,可以看到 HEAD指针已经指向了原来的 add file4对应的commit对象,也就是 master分支指针指向的对象,且commit记录已经正确修改:

(base) orion-orion@MacBook-Pro Learn-Git % git log
commit f4fd696d9b2ff0649adf89e93f7fd767d7c37922 (HEAD -> master)
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:43 2022 +0800

    add file4

commit 27de88d3e66b36f67b3d0eec45759e365951819c
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:27 2022 +0800

    add file3

commit 19f39c0883a0b5b9e7ebcef4e9efd82c72e8bb44
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:11 2022 +0800

    add file2

commit b04ef5bbf5bf70296790c7e9fbe71b27afd04a3f
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:38:46 2022 +0800

    add file1
</orion-orion@foxmail.com></orion-orion@foxmail.com></orion-orion@foxmail.com></orion-orion@foxmail.com>

此处的 HEAD分支指向了最新的commit对象,也就意味着和 master分支重合了,它们的关系如下图所示:

Git技法:.gitignore、移除暂存与撤销修改

PS:注意,Git的默认分支名字就是 master, 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 它会在每次的提交操作中自动向前移动,所以默认的 master分支会一直指向最新的提交对象。
但同样需要注意的是,Git 的 master分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master分支,是因为 git init命令默认创建它,并且大多数人都懒得去改动它。

最后,我们再说一下 HEAD~{n}(Tilde)和 HEAD^{n}(Caret)的区别。在大多数情况下, ~ 表示在提交树上直接进行线性回溯(默认每个节点都选择其第一个parent),而 ^ 则用于在提交树上选择当前节点的不同parent。注意, 在提交树没有分叉时,二者等价

我们以下面这个提交树为例子来讲解。这里有上到下对应由远及近的时间顺序,其中D、F、B、A都为merge commit。节点B和C是A节点的parents,parents按照从左到右的提交顺序。

G   H   I   J
 \ /     \ /
  D   E   F
   \  |  / \
    \ | /   |
     \|/    |
      B     C
       \   /
        \ /
         A

[TencentCloudSDKException] code:FailedOperation.ServiceIsolate message:service is stopped due to arrears, please recharge your account in Tencent Cloud requestId:ea663f86-1ad5-4e8e-94ac-317e4edf645e

[En]

[TencentCloudSDKException] code:FailedOperation.ServiceIsolate message:service is stopped due to arrears, please recharge your account in Tencent Cloud requestId:fdce2fa0-b698-4d47-8491-203077e2bd66

A = A~0  = A^0
B = A^   = A^1     = A~1
C = A^2
D = A^^  = A^1^1   = A~2
E = B^2  = A^^2
F = B^3  = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2  = B^^2    = A^^^2  = A~2^2
I = F^   = B^3^    = A^^3^
J = F^2  = B^3^2   = A^^3^2

3.3 撤销 git commit 操作

现在我们想撤销 git commit的操作。我们回到在2.2节中提到的 git reset&#x547D;&#x4EE4;。不过现在我们需要使用 git reset --soft&#x65B9;&#x6CD5;&#xFF0C;&#x6BD4;&#x5982;&#x5982;&#x679C;&#x6211;&#x4EEC;&#x7528;git log`查看到以下修改记录:

(base) orion-orion@MacBook-Pro Learn-Git % git log
commit 3f1ea3da4bd477b511c5a6eb0ddc8620b0d3b34f (HEAD -> master)
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 15:46:23 2022 +0800

    add file4

commit 0755827bbb67c463cb7ac139f1f86c93006a26e0
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 15:45:58 2022 +0800

    add file3

commit 19f39c0883a0b5b9e7ebcef4e9efd82c72e8bb44
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:11 2022 +0800

    add file2

commit b04ef5bbf5bf70296790c7e9fbe71b27afd04a3f
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:38:46 2022 +0800

    add file1
</orion-orion@foxmail.com></orion-orion@foxmail.com></orion-orion@foxmail.com></orion-orion@foxmail.com>

我们想撤销掉上一次的 add file4对应的提交,需要运行 git reset --soft HEAD~1命令:

commit 0755827bbb67c463cb7ac139f1f86c93006a26e0 (HEAD -> master)
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 15:45:58 2022 +0800

    add file3

commit 19f39c0883a0b5b9e7ebcef4e9efd82c72e8bb44
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:11 2022 +0800

    add file2

commit b04ef5bbf5bf70296790c7e9fbe71b27afd04a3f
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:38:46 2022 +0800

    add file1
</orion-orion@foxmail.com></orion-orion@foxmail.com></orion-orion@foxmail.com>

命令中的 HEAD~1意思为将撤销掉上次提交的记录, HEAD~2则为撤销上上次提交之后的所有记录,其余以此类推。

git reset命令可连续使用(对于 --mixed--soft和我们下面将要介绍的 --hard选项都如此),比如我们再使用一次命令 git reset --soft HEAD~1就会再继续撤销掉上上次提交的记录:

commit 19f39c0883a0b5b9e7ebcef4e9efd82c72e8bb44 (HEAD -> master)
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:39:11 2022 +0800

    add file2

commit b04ef5bbf5bf70296790c7e9fbe71b27afd04a3f
Author: orion-orion <orion-orion@foxmail.com>
Date:   Mon Aug 1 14:38:46 2022 +0800

    add file1
</orion-orion@foxmail.com></orion-orion@foxmail.com>

注意, reset命令还可以选择 --hard选项。 --hard选项与 soft选项的区别在于,使用 --hard选项不仅会撤销掉最后一次的commit,同时也会修改working tree,也就是当前的工作目录。事实上, --hard标记是 reset命令唯一的危险用法,它也是 Git 会真正地销毁数据的仅有的几个操作之一。 其他任何形式的 reset调用都可以轻松撤消,但是 --hard选项不能,因为它强制覆盖了工作目录中的文件。因此,使用 --hard选项时要多加小心。

参考

Original: https://www.cnblogs.com/orion-orion/p/16303308.html
Author: orion-orion
Title: Git技法:.gitignore、移除暂存与撤销修改

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/562910/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球