摘要:上一集我们一起入门学习了git的基本概念和git常用的操作,包括提交和同步代码、使用分支、出现代码冲突的解决办法、紧急保存现场和恢复现场的操作。学会以后已经足够我们使用Git参加协作开发了,但是在开发的过程中难免会出错,本文主要介绍版本控制的过程中出错了的场景,以及Git开发的一些技巧,让我们用的更流畅。
上集回顾:
本文核心:
如果你发现刚刚的操作一不小心commit了,所幸你还没有推送到远程仓库,你可以用reset命令来撤消你的这次提交。
reset命令的作用:重置HEAD(当前分支的版本顶端)到另外一个commit。
我们的撤消当前提交的时候往往不希望我们此次提交的代码发生任何丢失,只是撤消掉commit的操作,以便我们继续修改文件。如果我们是想直接不要了这次commit的全部内容的任何修改我们将在下一小节讨论。
来,我们先说一句蠢话来diss老板
$ touch to_boss.txt
$ echo 'my boss is a bad guy!' > to_boss.txt
$ git add to_boss.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: to_boss.txt
$ git commit -m "[+]骂了我的boss"
[master 3d113a7] [+]骂了我的boss
1 file changed, 1 insertion(+)
create mode 100644 to_boss.txtmy boss is a bad guy!add然后status查看新文件已经加入跟踪commit提交了这次的修改好了,刚刚我们“不小心”diss了我们的老板,要是被发现就完了,所幸还没有push,要快点撤消这些提交,再换成一些好话才行。
我们使用以下命令:
$ git reset --soft head^
$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: to_boss.txt
$ cat to_boss.txt
my boss is a bad guy!
$ echo 'my boss is a good boy!'
my boss is a good boy!
$ echo 'my boss is a good boy!' > to_boss.txt
$ cat to_boss.txt
my boss is a good boy!
$ git add to_boss.txt
$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: to_boss.txt
$ git commit -m "[*]夸了我的boss"
[master 8be46aa] [*]夸了我的boss
1 file changed, 1 insertion(+)
create mode 100644 to_boss.txtgit reset --soft head^撤消了本次提交,将工作区恢复到了提交前但是已经add的状态to_boss.txt的内容改成了my boss is a good boy!add然后commit提交好了,有惊无险,这就是撤消commit的操作。另一种情况是如果你想撤消commit的时候支持舍弃这次全部的修改就把git reset --soft head^改成git reset --hard head^,这样你本地修改就彻底丢掉了(慎用),如果真用了想找回来怎么办?见救命的后悔药。
当然了,你只要开心不加soft或hard参数也是安全的(相当于使用了--mixed参数),只不过是撤消以后你的本次修改就会回到add之前的状态,你可以重新检视然后再做修改和commit。
要是我们做的更过分一点,直接把这次commit直接给push怎么办?要是被发现就全完了,我们来看看github上的远程仓库。

完了,真的提交了(我刚刚push的)让我们冷静下来,用撤消当前commit的方法先撤消本地的commit,这次我们来试试用hard参数来撤消
$ git reset --hard head^
HEAD is now at 3f22a06 [+]add file time.txt
$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
$ git push origin master --force
Total 0 (delta 0), reused 0 (delta 0)
To github.com:pzqu/git_test.git
+ 3d113a7...3f22a06 master -> master (forced update)git reset --hard head^回滚到上一个commitgit status查看现在的工作区情况,提示Your branch is behind 'origin/master' by 1 commit,代表成功表了上一次的提示状态,nothing to commit, working tree clean代表这次的修改全没了,清理的算是一个彻底。如果还想找回来怎么办,我们还真是有办法让你找回来的,见救命的后悔药。git push origin master --force 命令强制提交到远程仓库(注意,如果是在团队合作的情况下,不到迫不得已不要给命令加--force参数)
让我们看看github
真的撤消了远程仓库,长舒一口气。
如果我们刚刚执行了git reset --soft或者add等的操作,把一些东西加到了我们的暂存区,比如日志文件,我们就要把他们从暂存区拿出来。
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: mysql.log
$ git reset -- mysql.log
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
mysql.log
nothing added to commit but untracked files present (use "git add" to track)status查看暂存区,里面有一个mysql.log被放进去了git reset -- mysql.log把mysql.log取出来status可以看到真的取出来了
然后如果不要想这个文件的话再rm掉就好啦,但是如果这些文件每次自动生成都要用这种方式取出暂存区真的好累,我们可以用 git忽略不想提交的文件当我们想要把某个文件任意的回滚到某次提交上,而不改变其他文件的状态我们要怎么做呢?
我们有两种情况,一种是,只是想在工作区有修改的文件,直接丢弃掉他现在的修改;第二种是想把这个文件回滚到以前的某一次提交。我们先来说第一种:
$ cat time.txt
10:41
$ echo 18:51 > time.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: time.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ cat time.txt
18:51
$ git checkout -- time.txt
$ cat time.txt
10:41time.txt的内容,可以status看到他发生了变化git checkout -- time.txt , 取消这次在工作区的修改,如果他已经被add加到了暂存区,那么这个命令就没有用了,他的意思是取消本次在工作区的修改,去上一次保存的地方。如果没有add就回到和版本库一样的状态;如果已经加到了暂存区,又做了修改,那么就回加到暂存区后的状态将文件回滚到任意的版本我们这里说的把文件回滚到以前的某个版本的状态,完整的含义是保持其他文件的内容不变,改变这个文件到以前的某个版本,然后修改到自己满意的样子和做下一次的提交。核心命令
git checkout [<options>] [<branch>] -- <file>...我们还是用time.txt这个文件来做试验,先搞三个版本出来,在这里我已经搞好了,来看看:
版本1,time.txt内容00:50
commit 35b66ed8e3ae2c63cc4ebf323831e3b917d2b1d4 (HEAD -> master, origin/master, origin/HEAD)
Author: pzqu <pzqu@example.com>
Date: Sun Dec 23 00:51:54 2018 +0800
[*]update time to 00:50版本2,time.txt内容18:51
commit 856a74084bbf9b678467b2615b6c1f6bd686ecff
Author: pzqu <pzqu@example.com>
Date: Sat Dec 22 19:39:19 2018 +0800
[*]update time to 18:51版本3,time.txt内容10:41
commit 3f22a0639f8d79bd4e329442f181342465dbf0b6
Author: pzqu <pzqu@example.com>
Date: Tue Dec 18 10:42:29 2018 +0800
[+]add file time.txt现在的是版本1,我们把版本3检出试试。
$ git checkout 3f22a0639f8d -- time.txt
$ cat time.txt
10:41
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: time.txtcheckout+commit id+-- filename的组合,横跨版本2把历史版本3的time.txt搞出来了我们来把time.txt恢复到版本1,同样的方法,因为版本1是上一次提交我们可以省略掉版本号
$ git checkout -- time.txt
$ cat time.txt
00:50看到了吧!只要用git checkout commit_id -- filename的组合,想搞出哪个文件历史版本就搞出哪个。
到了这里,你可能会很懵比,reset和checkout命令真的好像啊!都可以用来做撤消
checkout语义上是把什么东西取出来,所以此命令用于从历史提交(或者暂存区域)中拷贝文件到工作目录,也可用于切换分支。reset语义上是重新设置,所以此命令把当前分支指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录。还想不通可以给我发邮件:pzqu@qq.com
来到这里我已经很清楚的你的现况了,你的代码丢了现在一定非常的着急,不要慌,总是有办法找回他们的。但是前提是要保证你的项目根目录下.git文件夹是完整的,要是手动删除了里面的一些东西那就真完了。还要保证一点,你的代码以前是有过git追踪的,最少add过
Git提供了一个命令git reflog用来记录你的每一次命令,贴个图吧直观点:

git reflog里的全部都是和改变目录树有关的,比如commit rebase reset merge,也就是说一定要有改变目录树的操作才恢复的回来git log是一样的,也可以看到所有分支的历史提交,不一样的是看不到已经被删除的 commit 记录和 reset rebase merge 的操作
我们可以看到git reflog前面的就是commit id,现在我们就可以用之前介绍过的方法来回滚版本了,撤消当前commit$ git reset --hard 856a740
HEAD is now at 856a740 [*]update time to 18:51
$ git log -1
commit 856a74084bbf9b678467b2615b6c1f6bd686ecff (HEAD -> master)
Author: pzqu <pzqu@example.com>
Date: Sat Dec 22 19:39:19 2018 +0800
[*]update time to 18:51
$ git reset --hard 35b66ed
HEAD is now at 35b66ed [*]update time to 00:50
$ git log -2
commit 35b66ed8e3ae2c63cc4ebf323831e3b917d2b1d4 (HEAD -> master, origin/master, origin/HEAD)
Author: pzqu <pzqu@example.com>
Date: Sun Dec 23 00:51:54 2018 +0800
[*]update time to 00:50
commit 856a74084bbf9b678467b2615b6c1f6bd686ecff
Author: pzqu <pzqu@example.com>
Date: Sat Dec 22 19:39:19 2018 +0800
[*]update time to 18:51git reflog返回的结果,用git reset --hard commit_id回退到856a740这个版本git log -1看近一行的日志,可以看到目前就在这了git reflog的结果,用git reset --hard 35b66ed跑到这次提交git log -2看到两次提交的日志,我们就这么再穿梭过来了,就是这么爽
但是我们如果只是想把此提交给找回来,恢复他,那还是不要用reset的方式,可以用cherry-pick或者merge来做合并你之前没有commit过的文件,被删除掉了,或者被reset --hard的时候搞没了,这种情况可以说是相当的难搞了,所幸你以前做过add的操作把他放到过暂存区,那我们来试试找回来,先来创建一个灾难现场
$ echo 'my lose message' > lose_file.txt
$ git add lose_file.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: lose_file.txt
$ git reset --hard 35b66ed8
HEAD is now at 35b66ed [*]update time to 00:50
$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
$ ls
README.md need_stash.txt share_file.txt time.txtlose_file.txt的文件并写入内容my lose message,并把他加到暂存区git reset --hard 35b66ed8用丢弃一切修改的方式来使现在的工作区恢复到35b66ed8版本,因为还没提交所以也就是恢复到当前的(head)版本。status和ls再看,这个叫lose_file.txt的文件真的没了,完蛋了,第一反应用刚刚学到的命令git reflow会发现根本就不好使核心命令:git fsck --lost-found,他会通过一些神奇的方式把历史操作过的文件以某种算法算出来加到.git/lost-found文件夹里
$ git fsck --lost-found
Checking object directories: 100% (256/256), done.
Checking objects: 100% (3/3), done.
dangling blob 7f5965523d2b9e850b39eb46e8e0f7c5755f6719
dangling commit fdbb19cf4c5177003ea6610afd35cda117a41109
dangling commit 8be46aa83f0fe90317b0c6b9c201ad994f8caeaf
dangling blob 11400c1d56142615deba941a7577d18f830f4d85
dangling tree 3bd4c055afedc51df0326def49cf85af15994323
dangling commit 3d113a773771c09b7c3bf34b9e974a697e04210a
dangling commit bfdc065df8adc44c8b69fa6826e75c5991e6cad0
dangling tree c96ff73cb25b57ac49666a3e1e45e0abb8913296
dangling blob d6d03143986adf15c806df227389947cf46bc6de
dangling commit 7aa21bc382cdebe6371278d1af1041028b8a2b09这里涉及到git的一些低层的知识,我们可以看到这里有blob、commit、tree类型的数据,还有tag等类型的。他们是什么含义呢?

blob组件并不会对文件信息进行存储,而是对文件的内容进行记录commit组件在每次提交之后都会生成,当我们进行commit之后,首先会创建一个commit组件,之后把所有的文件信息创建一个tree组件,所以哪个blob代表什么文件都可以在tree 里找到
我们来看看怎么恢复刚刚不见了的lose_file.txt文件,在上面执行完git fsck --lost-found命令,返回的第一行blob我们看看他的内容git show 7f5965523d2b9e850b39eb46e8e0f7c5755f6719
my lose message
git show 7f5965523d2b9e850b39eb46e8e0f7c5755f6719 > lose_file.txt
$ ls
README.md lose_file.txt need_stash.txt share_file.txt time.txtcommit tree的内容$ git cat-file -p fdbb19cf4c5177003ea6610afd35cda117a41109
tree 673f696143eb74ac5e82a46ca61438b2b2d3bbf4
parent e278392ccbf4361f27dc338c854c8a03daab8c49
parent 7b54a8ae74be7192586568c6e36dc5a813ff47cf
author pzqu <pzqu@example.com> 1544951197 +0800
committer pzqu <pzqu@example.com> 1544951197 +0800
Merge branch 'master' of github.com:pzqu/git_test
$ git ls-tree 3bd4c055afedc51df0326def49cf85af15994323
100644 blob c44be63b27a3ef835a0386a62ed168c91e680e87 share_file.txtgit cat-file -p可以看到commit的内容,可以选择把这个commit合并到我们的分支里,还是reset merge rebase cherry-pick这些命令来合commitgit ls-tree列出tree下面的文件名和id的记录信息,然后就可以根据这些来恢复文件了后记:
如果你发现执行git fsck --lost-found的输出找不到你想要的,那么在执行完git fsck --lost-found后会出现一堆文件 在 .git/lost-found 文件夹里,我们不管他。可以用以下命令来输出近期修改的文件
$ find .git/objects -type f | xargs ls -lt | sed 3q
-r--r--r-- 1 pzqu staff 32 12 23 12:19 .git/objects/7f/5965523d2b9e850b39eb46e8e0f7c5755f6719
-r--r--r-- 1 pzqu staff 15 12 23 01:51 .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
-r--r--r-- 1 pzqu staff 162 12 23 00:51 .git/objects/35/b66ed8e3ae2c63cc4ebf323831e3b917d2b1d4
$ git cat-file -t 7f5965523d2b9e850b39eb46e8e0f7c5755f6719
blob
$ git cat-file -p 7f5965523d2b9e850b39eb46e8e0f7c5755f6719
my lose message
$ git cat-file -t b2484b5ab58c5cb6ecd92dacc09b41b78e9b0001
tree
$ git cat-file -p b2484b5ab58c5cb6ecd92dacc09b41b78e9b0001
100644 blob f9894f4195f4854cfc3e3c55960200adebbc3ac5 README.md
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 need_stash.txt
100644 blob 83f50ec84c00f5935da8089bac192171cfda8621 share_file.txt
100644 blob f0664bd6a49e268d3db47c508b08d865bc25f7bb time.txtfind .git/objects -type f | xargs ls -lt | sed 3q返回了近3个修改的文件,想要更多就改3q这个数值,比如你想输出100个就用100qgit cat-file -t 7f5965523d2b9e850b39eb46e8e0f7c5755f6719 就能看见文件类型 把最后一个/去掉 复制从objects/ 后面的所有东西放在-t后面git cat-file -p id就能看见文件内容,是不是很爽有时候会碰到我们已经commit但是有修改忘记了提交,想把他们放在刚刚的commit里面,这种时候怎么做呢?
$ git log --name-status --pretty=oneline -1
35b66ed8e3ae2c63cc4ebf323831e3b917d2b1d4 (HEAD -> master, origin/master, origin/HEAD) [*]update time to 00:50
M time.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: lose_file.txt
new file: test_amend.txt
$ git commit --amend --no-edit
[master 31cc277] [*]update time to 00:50
Date: Sun Dec 23 00:51:54 2018 +0800
3 files changed, 2 insertions(+), 1 deletion(-)
create mode 100644 lose_file.txt
create mode 100644 test_amend.txt
$ git log --name-status --pretty=oneline -1
31cc2774f0668b5b7c049a404284b19e9b40dc5d (HEAD -> master) [*]update time to 00:50
A lose_file.txt
A test_amend.txt
M time.txttime.txtgit commit --amend --no-edit合并到上一个提交里,如果不加--no-edit参数的话,会提示你来修改commit提示信息(这个命令也可以用在重复编辑commit message)。标签是一个类似于快照的东西,常常用于测试和发布版本。所以我们常常把tag名以版本号来命名,比如:v1.0beat1这样
我们怎么创建标签呢?首先先切换到想打标签的分支,然后直接打就可以了。
$ git branch
dev/pzqu
master
* release_v1.0
$ git tag -a release_v1.0 -m "release v1.0"
$ git tag release_v1.1
$ git tag
release_v1.0
release_v1.1
$ git push --tags
Counting objects: 2, done.
Writing objects: 100% (2/2), 158 bytes | 158.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To github.com:pzqu/git_test.git
* [new tag] release_v1.0 -> release_v1.0
* [new tag] release_v1.1 -> release_v1.1tag的分支release_v1.0带有信息release v1.0的tagtag的提交信息的release_v1.1git tag查看tagtag也可以推送单个tag
$ git push origin release_v1.1
Total 0 (delta 0), reused 0 (delta 0)
To github.com:pzqu/git_test.git
* [new tag] release_v1.1 -> release_v1.1我们来删除tag
$ git tag -d release_v1.0
Deleted tag 'release_v1.0' (was eb5d177)
$ git push origin :refs/tags/release_v1.0
To github.com:pzqu/git_test.git
- [deleted] release_v1.0
$ git tag
release_v1.1release_v1.0的tagrelease_v1.0的tag 先看看当前的log
31cc277 (HEAD -> release_v1.0, tag: release_v1.1, origin/release_v1.0, master) [*]update time to 00:50
856a740 [*]update time to 18:51
3f22a06 [+]add file time.txt
4558a25 (origin/dev/pzqu, dev/pzqu) [*]test stash
d9e018e [*]merge master to dev/pzqu比方说要对[*]update time to 18:51这次提交打标签,它对应的commit id是856a740,敲入命令:
$ git tag v.9 856a740
$ git log --pretty=oneline --abbrev-commit
31cc277 (HEAD -> release_v1.0, tag: release_v1.1, origin/release_v1.0, master) [*]update time to 00:50
856a740 (tag: v0.9) [*]update time to 18:51我们有两种情况,一种是我们根本就不想这些文件出现在git库里比如日志文件;另一种是git远程仓库里有这些文件,就像通用的配置文件,我们必须要在本地修改配置来适应运行环境,这种情况下我们不想每次提交的时候都去跟踪这些文件。
忽略文件的原则是:
我们要怎么做呢?
在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。$ echo "*.log" > .gitignore$ touch test.log$ touch test2.log$ ls -a . .git README.md need_stash.txt test.log test_amend.txt .. .gitignore lose_file.txt share_file.txt test2.log time.txt$ git status On branch release_v1.0 nothing to commit, working tree clean* 创建并写入忽略规则`*.log`忽略全部以`.log`为后缀的文件 * 创建了`test.log`和`test2.log` * `status`查看,真是工作区是`clean`,新创建的文件没有被跟踪
核心命令:
git update-index —assume-unchanged 文件名
time.txt文件并写入10:41,提交到远程仓库git update-index —assume-unchanged加time.txt加到忽略名单里time.txt的内容为10:43status查看确实没有被跟踪
看远程仓库
核心命令:
git update-index —no-assume-unchanged 文件名
pull同步远程仓库,真的没有更新刚刚被添加跟踪忽略的文件git update-index —no-assume-unchanged取消跟踪忽略status查看,出现文件的跟踪如果忘记了哪些文件被自己本地跟踪

git update-index —assume-unchanged加time.txt加到忽略名单里git ls-files -v| grep '^h\ '命令可以看到小写h代表本地不跟踪的文件学完本文章,你将学会
理论上,git日常用到的命令是 diff show fetch rebase pull push checkout commit status 等,这些命令都不会导致代码丢失,假如害怕代码丢失,可以预先commit一次,再进行修改,但切记
不可使用自己不熟悉的命令 任何命令,不要加上-f的强制参数,否则可能导致代码丢失
建议多使用命令行,不要使用图形界面操作
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。