【Git Tutorial】一、gitignore和版本回退
没错,这是一篇git教程,又名《一个往github上push文件,图方便把数据集放在了项目文件夹,然后发现因为文件过大而传不上去的CS人破防实录》
从gitignore说起
什么是gitignore?顾名思义,就是让git在处理过程中忽略的文件。比如我在本地git仓库目录下放了一个大文件夹,但不希望这个文件夹被提交到远程仓库,那我就可以通过在本地仓库文件夹的根目录创建名为.gitignore
的文本文件来解决这件事,文件名前的点表示隐藏的文件。在.gitignore
文件中提及的文件/目录都不会被git追踪,自然也不会被提交到远程。一个.gitignore
的文件示例如下:
1 |
|
但如果你像我一样,先把大文件夹commit了,并且提交到了远程,但github远程仓库不接受大小如此之大的提交,所以本次提交被拒绝了。这时候你会发现,在.gitignore
里添加这个大文件夹也于事无补,因为——
这个巨大的文件夹已经被加入git的追踪了。
那么怎么办呢?我现在希望这个文件夹从此以后不要被git追踪,并且希望本次push提交的操作要成功。
于是我开始查找原因。
为什么gitignore的文件仍然会被git追踪
如果在 .gitignore
文件中添加了某个文件,但该文件仍然被 Git 追踪,可能有几个原因:
- 已经提交过的文件:如果该文件已经被提交到版本控制中,即使后来在
.gitignore
中添加了它,Git 仍然会继续追踪该文件。.gitignore
只对尚未被追踪的文件起作用。 - 缓存中的文件:有时候即使将文件添加到
.gitignore
中,但如果该文件已经在 Git 缓存中,Git 仍然会继续追踪它。你可以通过以下步骤解决这个问题:- 首先确保在
.gitignore
文件中正确地指定了该文件的路径模式。 - 然后从 Git 缓存中删除该文件:
git rm --cached 文件名
- 最后提交修改:
git commit -m "Remove <file> from tracking"
- 首先确保在
- 全局设置的忽略规则不生效:有时候,如果在全局 Git 配置中设置了全局的忽略规则,而你在项目中的
.gitignore
文件中又有冲突的规则,可能会导致混淆。确保你在项目中的.gitignore
文件中的规则不会被全局设置的规则覆盖。
最初,我不知道我的这种情况数据集大文件夹是在缓存中还是被当作了“已经提交的文件”。所以我从回顾git的每一步开始分析(悲)。
Git使用步骤回顾和问题解决
在使用 Git 进行版本控制时,通常会经历三个区域:
- 工作目录(Working Directory):包含实际的项目文件。
- 暂存区(Staging Area):一个缓存区域,用于临时存放工作目录的改动,等待提交到 Git 仓库。又称为索引(index),本质为一个二进制文件,存储于
.git
目录的index
文件中。 - 仓库(Repository):Git 仓库保存项目的元数据和对象数据库,包括完整的项目历史记录。
来回顾一下我要做什么:我本来有一个远程仓库,以及和它完全一样的本地仓库。
但我在本地写了新的代码,也就是在本地仓库修改了工作目录的内容并通过git add .
命令将当前工作目录的所有修改提交到暂存区。然后我使用了git commit
命令将修改从暂存区提交到本地仓库,最后我使用git push
将本地仓库推送到远程。
如果我想取消这次提交,应该从哪个步骤着手?
git add
首先是git add
。我去查了git的文档:https://git-scm.com/docs,其中提到`git add`命令的作用:
Add file contents to the index
也就是说,git add
的作用是,通过更新索引(index)将工作目录中的文件添加到暂存区,使得 Git 能够跟踪这些文件的变化。也就是说,此时的大文件夹已经被git追踪了,这时候再通过.gitignore
加新东西也没用了。
合理的,那我就从git缓存(索引)里把那个多加的大文件删掉呗!于是我试图使用
1 |
|
以把文件(夹)从git索引里移除。然后我确定我的.gitignore
文件里有这个大文件夹的名字,并再次通过add-commit-push三步指令试图把本地仓库提交到远程,仍然失败——仍然提交了一个大文件夹。
git status & git push
太怪了!
于是我去问了GPT,我如何查看某次git提交即将交什么,希望从查提交文件列表里发现端倪。
GPT告诉我:
可以使用
git status
命令来显示工作目录和暂存区的状态信息,其中会列出暂存区中被修改或添加的文件。文件列表将会出现在 “Changes to be committed” 部分下面。
于是我使用了git status
,而结果是:
1 |
|
也就是说,我的本地分支已经领先远程分支4次提交了。(本文所涉及的仓库只有分支,分支就是整个仓库)
而git push命令的逻辑是:
git push
命令会将本地分支上的所有提交按顺序推送到远程仓库。远程仓库接收到这些提交后,会按照它们在本地分支上的顺序来存储和记录。当你运行
git push
命令时,Git 会将本地分支上自上次推送以来新增的所有提交(即本地分支领先于远程分支的提交)推送到远程仓库。这些提交会按照它们在本地分支上的顺序被推送到远程仓库,并在远程仓库上创建一个相应的提交历史记录。所以,如果你在本地分支上进行了多次提交,
git push
会将这些提交依次推送到远程仓库,并在远程仓库上创建相应的提交记录,以保持提交的顺序一致。
好极了!也就是说,因为第$x$次没提交成功,我把文件夹从索引里删掉了,紧接着创建了第$x+1$个提交。虽然第$x+1$个提交里没有那个大文件夹,但是第一次提交里有啊!
所以只改索引不管用啊,因为每次git push
操作都会从第一个提交开始推送到远程,而第一个提交里包含了一个大文件夹,所以……
此时我突然明白了那句“文件已经被提交到版本控制”是什么意思。这里的“提交到版本控制”指的应该是commit
到某个仓库(包括本地仓库),而不是我原来理解的commit
和push
到远程。
那现在要做的事情也很清晰了,我需要把这几次本地提交记录都删了,但保留我的源文件,也就是某种版本回退。
git reset
首先想知道现在本地有多少版本的提交,可以通过git log
查看。
可以看到有指针的存在。
然后我想删除提交记录,但保留工作区的文件不变(也就是说不是连同文件一起的版本回退)。这时候就可以使用
1 |
|
这里git reset命令的帮助文档里解释的很清晰:https://git-scm.com/docs/git-reset。相反地,`--hard`就是直接版本回退到提交之前,并且工作目录里的文件也会相应地被修改,回退到指针指向的提交的状态。
使用soft只删除提交但不删除文件之后,重新进行add-commit-push三步,终于成功将本地修改推送到远程了,一把辛酸泪。
关于Git索引和working tree
最后,索引其实几乎就是暂存区。了解索引的工作原理对理解git的工作流程很有帮助,此处不再赘述,放几个参考文献(逃)
Git索引(Index)到底包含了什么|极客教程 (geek-docs.com)
git深入理解(一):暂存区(Stage),索引(index)_git index-CSDN博客
git–一文弄懂git的工作区、索引区、本地仓库、远程仓库以及add、commit、push三个操作 - at_today - 博客园 (cnblogs.com)