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目录下所有文件及子目录(不包括data和model目录本身)。但是我们会发现,实际上空的data和model目录并没有成功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)。但是如果我们想保存data和model的目录架构呢?很简单,我们只需要在data和model目录下添加.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 <file>..." to unstage)
        new file:   data/.gitkeep
        new file:   model/.gitkeep
但是需要注意,如果这样写就没用:
# 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 add了一次,那么它就会被跟踪(track)并加入暂存区。此后再对其进行修改,Git会提醒你Changes not staged for commit与modified:   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")
而文件的所谓的未跟踪(untracked)、未修改(unmodified)、已修改(modified)、已暂存(staged)四种状态的关系如下所示:

2.2 清除已暂存的文件
现在假设我们搞忘了编写.gitignore,然后已经用了git add -A或git add .命令目录下所有文件及子目录都暂存了(在Git 2.0中git add -A或git add .命令等效)。而其中有很大的日志文件或一些诸如*.a的编译文件,我们如何将这些文件从暂存区域移除以取消跟踪呢?可以用git rm --cached命令完成此项工作,如:
git rm --cached README.md
注意要带上选项--cached,而不仅仅是git rm,git 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
注意到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,还有个参数为--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")
注意到Changes not staged for commit:与     modified:   README.md。说明该命令只是将README.md移除暂存区,但是上次对README.md的commit还在(即撤销最近的一次commit之后的变化)。
如果要递归地将当前目录下的所有文件及子目录移除出暂存区(与commit tree),可以这样写:
git rm -r --cached . 
注意这个命令非常危险和暴力,一般还是建议指定具体的目录或文件名。
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: 猎户座 <46917784+orion-orion@users.noreply.github.com>
Date:   Sun May 22 09:48:22 2022 +0800
    Initial commit
默认不用任何参数的话,git log会按提交时间列出所有的更新,最近的更新排在最上面。 正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址(如果电子邮件名为<46917784+orion-orion@users.noreply.github.com>,说明你在GitHub中将邮件名设置为私有的了,需要去修改一下)、提交时间以及提交说明。
3.2 追加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:
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: 猎户座 <46917784+orion-orion@users.noreply.github.com>
Date:   Sun May 22 09:48:22 2022 +0800
    Initial commit
3.3 撤销git commit操作
现在我们想撤销git commit的操作。我们回到git reset命令。不过现在我们需要使用git reset --hard方法:
(base) orion-orion@MacBook-Pro Learn-Git %  git reset --hard HEAD^1
HEAD is now at ab7bf6e add .gitignore
(base) orion-orion@MacBook-Pro Learn-Git % git log
commit ab7bf6e2c400c8d775cc3bc56928c7748c63c8f8 (HEAD -> main)
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: 猎户座 <46917784+orion-orion@users.noreply.github.com>
Date:   Sun May 22 09:48:22 2022 +0800
    Initial commit
命令中的HEAD^1意思为将commit记录回退到上上次提交后的状态,HEAD^2以此类推。
不过大家必须注意,--hard 标记是reset命令唯一的危险用法,它也是 Git 会真正地销毁数据的仅有的几个操作之一。 其他任何形式的reset调用都可以轻松撤消,但是--hard选项不能,因为它强制覆盖了工作目录中的文件。
参考
- [1] 《Pro Git 中文版》在线阅读
 - [2] Stack Overflow: How can I Remove .DS_Store files from a Git repository?
 - [3] Stack Overflow: How can I add a blank directory to a Git repository?
 - [4] Local Coder: Difference between .gitignore rules with and without trailing slash like /dir and /dir/
 - [5] Stack Overflow: Difference between "git add -A" and "git add ."
 - [6] 知乎:为什么要先 git add 才能 git commit ?
 - [7] 知乎:Git commits历史是如何做到如此清爽的?
 - [8] Stack Overflow: "git rm --cached x" vs "git reset head -- x"?
 
Git技法:.gitignore、移除暂存与撤销修改的更多相关文章
- 小丁带你走进git的世界二-工作区暂存区分支
		
小丁带你走进git的世界二-工作区暂存区分支 一.Git基本工作流程 1.初始化一个仓库 git init git clone git仓库分为两种情况: 第一种是在现有项目或目录下导入所有文件到 ...
 - 【Git】(1)---工作区、暂存区、版本库、远程仓库
		
工作区.暂存区.版本库.远程仓库 一.概念 1.四个工作区域 Git本地有四个工作区域:工作目录(Working Directory).暂存区(Stage/Index).资源库(Repository或 ...
 - Git学习(三)——暂存区、远程仓库、增删改管理
		
一.工作区和暂存区 工作区(Working Directory) 就是在你的电脑里能看到的目录 版本库(Repository) 工作区中的一个隐藏目录.git,这个不算工作区,而是Git版本库.Git ...
 - Git教程之工作区和暂存区(5)
		
工作区(Working Directory) 就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工作区:
 - git教程:工作区和暂存区
		
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念. 先来看名词解释. 工作区(Working Directory) 就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工 ...
 - Git教程之工作区和暂存区
		
工作区(Working Directory) 就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工作区:
 - 版本控制Git(1)——理解暂存区
		
一.svn和Git的比较 我们都知道传统的源代码管理都是以服务器为中心的,每个开发者都直接连在中间服务器上, 本地修改,然后commit到svn服务器上.这种做法看似完美,但是有致命的缺陷. 1. 开 ...
 - git学习笔记 ---工作区和暂存区
		
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念. 先来看名词解释. 工作区(Working Directory) 就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工 ...
 - [git 学习篇]工作区和暂存区
		
1 工作区,就是目录/User/my./learngit 2 版本库 工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库. liuzhipeng@exdroid43:~/pad/pad- ...
 
随机推荐
- JS练习实例--编写经典小游戏俄罗斯方块
			
最近在学习JavaScript,想编一些实例练练手,之前编了个贪吃蛇,但是实现时没有注意使用面向对象的思想,实现起来也比较简单所以就不总结了,今天就总结下俄罗斯方块小游戏的思路和实现吧(需要下载代码也 ...
 - 【Android开发】【布局】几个常用布局构成的简单demo
			
图image1.jpg,就是常用的 底部菜单栏 + Fragment联动 使用 RadioGroup + Fragment 图image2.jpg ,就是 TabLayout + ViewPager ...
 - Android去掉标题头
			
在AndroidManifest.xml文件中定义 <application android:theme="@android:style/Theme.NoTitleBar"& ...
 - Python raise...from... 是啥?
			
调试程序时看某些库的源代码,发现有如下代码读不懂,不理解后面这个from干什么用的. try: ... except KeyError: raise **Error('') from None try ...
 - 面试官:Zookeeper怎么解决读写、双写并发不一致问题,以及共享锁的实现原理?
			
哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 今天清明假期,赶上北京玉渊潭公园 ...
 - 如何科学衡量广告投放效果?HMS Core分析服务助您科学归因
			
日益多元化的广告形式以及投放成本的不断攀升,让广告主们更加关注每一次广告投放带来的实际价值. 然而,广告主一般仅能从平台获得展示.点击.下载等前端效果字段,实际的用户注册.激活等后端深度转化指标并无法 ...
 - mybatis在if标签里判断字符串相等
			
https://www.cnblogs.com/westward/p/6910856.html
 - ES Bridge跨链桥服务升级,新增BSC跨链网络
			
3月15日,Equal Sign Bridge(ES Bridge)跨链桥宣布新增BSC跨链网络,方便更多用户参与到ES Bridge的建设与发展,未来还将持续拓展更多的主流跨链币种,提升各链间的互操 ...
 - Java语言学习day19--7月25日
			
今日内容介绍1.继承2.抽象类3.综合案例---员工类系列定义 ###01继承的概述 *A:继承的概念 *a:继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系 *b:在Jav ...
 - 【审视】Scrum Master的检查清单
			
一般情况下,一个Scrum Master如果更多的是做组织会议.确保时间盒以及对流程中的障碍快速响应等事项的话,可以同时引导2-3个团队.在这种情况下,团队会在降低问题发生率的基础上提高一定的绩效. ...