【Git Tutorial】一、gitignore和版本回退

没错,这是一篇git教程,又名《一个往github上push文件,图方便把数据集放在了项目文件夹,然后发现因为文件过大而传不上去的CS人破防实录》

从gitignore说起

什么是gitignore?顾名思义,就是让git在处理过程中忽略的文件。比如我在本地git仓库目录下放了一个大文件夹,但不希望这个文件夹被提交到远程仓库,那我就可以通过在本地仓库文件夹的根目录创建名为.gitignore的文本文件来解决这件事,文件名前的点表示隐藏的文件。在.gitignore文件中提及的文件/目录都不会被git追踪,自然也不会被提交到远程。一个.gitignore的文件示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Ignore compiled binaries
*.exe
*.dll
*.so

# Ignore log files
*.log

# Ignore directory
logs/

# Ignore sensitive information
secret_key.txt

# Ignore IDE settings
.vscode/

但如果你像我一样,先把大文件夹commit了,并且提交到了远程,但github远程仓库不接受大小如此之大的提交,所以本次提交被拒绝了。这时候你会发现,在.gitignore里添加这个大文件夹也于事无补,因为——

这个巨大的文件夹已经被加入git的追踪了。

那么怎么办呢?我现在希望这个文件夹从此以后不要被git追踪,并且希望本次push提交的操作要成功。

于是我开始查找原因。

为什么gitignore的文件仍然会被git追踪

如果在 .gitignore 文件中添加了某个文件,但该文件仍然被 Git 追踪,可能有几个原因:

  1. 已经提交过的文件:如果该文件已经被提交到版本控制中,即使后来在 .gitignore 中添加了它,Git 仍然会继续追踪该文件。.gitignore 只对尚未被追踪的文件起作用。
  2. 缓存中的文件:有时候即使将文件添加到 .gitignore 中,但如果该文件已经在 Git 缓存中,Git 仍然会继续追踪它。你可以通过以下步骤解决这个问题:
    • 首先确保在 .gitignore 文件中正确地指定了该文件的路径模式。
    • 然后从 Git 缓存中删除该文件:git rm --cached 文件名
    • 最后提交修改:git commit -m "Remove <file> from tracking"
  3. 全局设置的忽略规则不生效:有时候,如果在全局 Git 配置中设置了全局的忽略规则,而你在项目中的 .gitignore 文件中又有冲突的规则,可能会导致混淆。确保你在项目中的 .gitignore 文件中的规则不会被全局设置的规则覆盖。

最初,我不知道我的这种情况数据集大文件夹是在缓存中还是被当作了“已经提交的文件”。所以我从回顾git的每一步开始分析(悲)。

Git使用步骤回顾和问题解决

在使用 Git 进行版本控制时,通常会经历三个区域:

  1. 工作目录(Working Directory):包含实际的项目文件。
  2. 暂存区(Staging Area):一个缓存区域,用于临时存放工作目录的改动,等待提交到 Git 仓库。又称为索引(index),本质为一个二进制文件,存储于.git目录的index文件中。
  3. 仓库(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 -rm --cached filename

以把文件(夹)从git索引里移除。然后我确定我的.gitignore文件里有这个大文件夹的名字,并再次通过add-commit-push三步指令试图把本地仓库提交到远程,仍然失败——仍然提交了一个大文件夹。

git status & git push

太怪了!

于是我去问了GPT,我如何查看某次git提交即将交什么,希望从查提交文件列表里发现端倪。

GPT告诉我:

可以使用git status 命令来显示工作目录和暂存区的状态信息,其中会列出暂存区中被修改或添加的文件。文件列表将会出现在 “Changes to be committed” 部分下面。

于是我使用了git status,而结果是:

1
2
3
On branch main
Your branch is ahead of 'origin/main' by 4 commits.
(use "git push" to publish your local commits)

也就是说,我的本地分支已经领先远程分支4次提交了。(本文所涉及的仓库只有分支,分支就是整个仓库)

而git push命令的逻辑是:

git push 命令会将本地分支上的所有提交按顺序推送到远程仓库。远程仓库接收到这些提交后,会按照它们在本地分支上的顺序来存储和记录。

当你运行 git push 命令时,Git 会将本地分支上自上次推送以来新增的所有提交(即本地分支领先于远程分支的提交)推送到远程仓库。这些提交会按照它们在本地分支上的顺序被推送到远程仓库,并在远程仓库上创建一个相应的提交历史记录。

所以,如果你在本地分支上进行了多次提交,git push 会将这些提交依次推送到远程仓库,并在远程仓库上创建相应的提交记录,以保持提交的顺序一致。

好极了!也就是说,因为第$x$次没提交成功,我把文件夹从索引里删掉了,紧接着创建了第$x+1$个提交。虽然第$x+1$个提交里没有那个大文件夹,但是第一次提交里有啊!

所以只改索引不管用啊,因为每次git push操作都会从第一个提交开始推送到远程,而第一个提交里包含了一个大文件夹,所以……

此时我突然明白了那句“文件已经被提交到版本控制”是什么意思。这里的“提交到版本控制”指的应该是commit到某个仓库(包括本地仓库),而不是我原来理解的commitpush到远程。

那现在要做的事情也很清晰了,我需要把这几次本地提交记录都删了,但保留我的源文件,也就是某种版本回退。

git reset

首先想知道现在本地有多少版本的提交,可以通过git log查看。

可以看到有指针的存在。

然后我想删除提交记录,但保留工作区的文件不变(也就是说不是连同文件一起的版本回退)。这时候就可以使用

1
git reset --soft HEAD^(或者其他commit的HASH值)

这里git reset命令的帮助文档里解释的很清晰:https://git-scm.com/docs/git-reset。相反地,`--hard`就是直接版本回退到提交之前,并且工作目录里的文件也会相应地被修改,回退到指针指向的提交的状态。

使用soft只删除提交但不删除文件之后,重新进行add-commit-push三步,终于成功将本地修改推送到远程了,一把辛酸泪。

关于Git索引和working tree

最后,索引其实几乎就是暂存区。了解索引的工作原理对理解git的工作流程很有帮助,此处不再赘述,放几个参考文献(逃)

Git官方文档

Git索引(Index)到底包含了什么|极客教程 (geek-docs.com)

git reset –soft命令的使用-CSDN博客

git深入理解(一):暂存区(Stage),索引(index)_git index-CSDN博客

git–一文弄懂git的工作区、索引区、本地仓库、远程仓库以及add、commit、push三个操作 - at_today - 博客园 (cnblogs.com)


【Git Tutorial】一、gitignore和版本回退
https://eipi15926.github.io/2024/04/19/20240419-gitignore/
作者
溟溯
发布于
2024年4月19日
许可协议