在实习当中经常用到Git,有碰到一些问题在此记录一下,具体Git入门教程可以参考廖雪峰写的教程,这里提及一下我觉得挺好用的两个命令git stash
和git rebase
。再说这两个命令之前,先来回顾一下Git的各个工作区域。
Git的工作区域和文件状态

Workspace: 工作区,就是你平时存放项目代码的地方
Index / Stage: 暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息
Repository: 仓库区(或版本库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本
Remote: 远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换

Untracked: 未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过git add 状态变为Staged.
Unmodify: 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为Modified. 如果使用git rm移出版本库, 则成为Untracked文件
Modified: 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过git add可进入暂存staged状态, 使用git checkout 则丢弃修改过, 返回到unmodify状态, 这个git checkout即从库中取出文件, 覆盖当前修改
Staged: 暂存状态. 执行git commit则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify状态. 执行git reset HEAD filename取消暂存, 文件状态为Modified
以上内容copy自这篇文章。这里主要要清楚的是,workspace并不指的是你当前文件夹的所有文件,而是tracked的文件,如果一份新建的文件处于Untracked的状态,那么该文件是不参与版本控制的,后续的git stash
操作也自然对他不会起作用。
Git Stash
假设你现在正在开发一套代码,你也已经有了好几个commit或者是有了好几个版本,这个时候你的上司突然叫你切回原来的稳定版本给别人看一下稳定版本的效果。当然你可以直接git commit
当前的工作区,提交一个commit,但是你当前的工作区并不是一个阶段性的小成果,你也不想提交这个从而产生一个没有太多参考价值的trash commit,那么这时git stash
就是给你的良药。他的作用是将你当前的工作区(workspace)和暂存区(Index)打包存好。
现在我们开始实验,首先新建个文件夹learnGit
,然后用以下指令初始化
1 | git init |
接着我们创建我们的第一个文件夹test1.txt
,并往里面写入一些东西
1 | touch test1.txt |
我们先提交这个作为第一个commit
1 | git add . |
我们按照相同方式再次提交第二个commit
1 | echo "Line 2 for test1.txt" >> test1.txt |
这时候你接着工作,给test1.txt
增加了第三行,并且将更改放到了暂存区;给test1.txt
增加了第四行,还没有放到暂存区;新建了一个新文件叫test2.txt
,并且给它写了第一行,还没有track这个文件。以上活动为以下指令
1 | 给test1.txt增加第三行,并放到暂存区 |
这个时候使用git status
查看一下当前git状态
1 | On branch master |
上面三个操作,分别对应着三种文件状态,第一种是已经被放到了暂存区,但还未提交;第二种是更改了还没有放进暂存区,但是文件本身是被track的;第三种就是文件都还未加入到git管理。这三种也典型地代表了几种平时会遇到的几种文件状态。这时候你的上司让你回到第一个commit,想看看那个稳定版本的效果,但是你不想提交当前的,因为什么都还没改完,我们就可以用git stash
储存当前的更改
1 | git stash save "first stash" |
输出为
1 | Saved working directory and index state On master: first stash |
其实看到这行的时候,你就应该意识到问题就出现了,你的test2.txt
并没有被track,所以理论上它是没有被存进去的。使用git status
查看一下当前的状态
1 | On branch master |
确实如你所担心的那样,当前的状态并不干净,test2.txt
还在这里,而对test1.txt
的两个更改已经消失了(被存进去了),你还是不能这时候回到以前的commit。所以我们先把刚刚储存的弹出,tracktest2.txt
文件,再存进去。先用git stash list
看看存了哪些
1 | stash@{0}: On master: first stash |
如果你之前没有存其他的话,前一个的存储标号应为0
,我们用git stash pop stash@{NUM}
将其弹出,NUM
填写你要弹出的stash
1 | git stash pop stash@{0} |
现在的工作区就是你存之前的状态了
stash的使用方法,除了用
git stash pop
之外还可以用git stash apply
,使用方法类似,正如名字所描述的那样,pop
直接会将你的stash弹出,而apply
则使用当前stash之后还会保留存的stash。
这时候我们用git status
查看一下当前的状态
1 | On branch master |
可以看到不一样的在于,之前放到暂存区的test1.txt
的第三行,现在也在工作区(workspace)了。
接下来,我们还来制作stash,将所有的更改放到暂存区,然后再存储stash
1 | git add . |
用git status
查看一下当前状态
1 | On branch master |
搞定,就像你什么更改都没做一样,回到了之前的状态,这时候你就可以开心地checkout
到其他的提交或者分支了
Git Rebase
在实习的过程中,碰到了一个很尴尬的问题,我用的是前同事的电脑,在提交修改的时候还没把本机的作者名和邮箱改过来,用的还是前同事的,但是他已经离职了,也就导致那一份提交push不到远程仓库里。这时候就要用git rebase
修改以前的提交了。
还是用前面的Git工作区,用git log
查看提交过的commit
1 | commit fff69d75f45e1e1689c19d0c12bda7c93584a92f (HEAD -> master) |
假设我们要更改第二个提交,也就是second commit
这一次,将它的作者改为geziyun
,邮箱改为ziyun_ge@foxmail.com
。
先使用git rebase -i <COMMIT_ID>
,这里的<COMMIT_ID>
需要选第一次提交的,它显示的其实是<COMMIT_ID>
之后的提交。可以看到以下交互窗口
1 | pick fff69d7 second commit |
可以看到第二次提交的的Commit ID,我们需要将第一行的pick
改为edit
,修改方式就跟vim用法一致,接着保存退出。
接着来更改作者和邮箱
1 | git commit --amend --author="geziyun <ziyun_ge@foxmail.com>" |
这里我们先暂停一下,看一下这一步做了什么操作。git commit
肯定是提交了一个commit,但是是哪里提交呢,可以用git log
查看一下。
1 | commit 90b922390a3fb62a9bc8e5af54e8a7a83f9a08a4 (HEAD) |
可以有趣地发现,第一次提交还是一样的,但是第二次提交的作者和邮箱都变了,最重要的是第二次提交的Commit ID也变了,也就是说这并不是真正意义上的“第二次提交”。为了更好的阐述这个问题,如果你使用的是VSCode的话,可以下载安装一个插件Git Graph。

可以看到我们当前的这次commit其实是在first commit
上的另一个分支,原来的second commit
在另一个分支(还可以看到之前我们保存的stash)
接着我们使用以下指令
1 | git rebase --continue |
可以看到master指针已经指向了你新的提交,注意这里还可以看到老的second commit
,这是因为之前的stash是在老的second commit
上修改的。你可以使用git stash pop
弹出之前储存的stash(没有指定哪个stash的话就默认会弹出最近的一个stash),由于你只改了作者和邮箱,所以你还是不用解冲突。然后你可以惊喜地发现老的second commit
不见了,是因为它没有在哪个分支,所以就不显示出来了。如下图,这样我们的作者和邮箱就顺利修改完毕了,可以愉快地push到远程仓库了。

根据上述的描述,其实你大致也可以猜出,git rebase
实际上新建一个暂时的分支,然后再那上面修改一些东西(不仅仅可以修改commit的作者,你还可以修改代码细节),最后再和之前的master分支进行合并,当然如果必要的话你还需要解冲突使得合并顺利。