本文由云+社区发表作者:工程师小熊摘要:上一集我们一起入门学习了git的基本概念和git常用的操作,包括提交和同步代码、使用分支、出现代码冲突的解决办法、紧急保存现场和恢复现场的操作。学会以后已经足够我们使用Git参加协作开发了,但是在开发的过程中难免会出错,本文主要介绍版本控制的过程中出错了的场景,以及Git开发的一些技巧,让我们用的更流畅。上集回顾:Git的基本概念一个人使用Git时的代码版本控制–(提交、拉代码、分支操作)多人合作时的代码版本控制–(合并冲突、暂存代码)本文核心:后悔药-各种后悔操作(撤消commit,回滚,回退远程仓库等)哎呀,提交的时候漏了文件tag操作git忽略不想提交的文件后悔药撤消当前commit如果你发现刚刚的操作一不小心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 statusOn branch masterYour 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.txt创建to_boss.txt文件,并向其写入了my boss is a bad guy!add然后status查看新文件已经加入跟踪commit提交了这次的修改好了,刚刚我们“不小心”diss了我们的老板,要是被发现就完了,所幸还没有push,要快点撤消这些提交,再换成一些好话才行。我们使用以下命令:$ git reset –soft head^$ git statusOn branch masterYour 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.txtmy 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.txtmy boss is a good boy!$ git add to_boss.txt$ git statusOn branch masterYour 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上的远程仓库。upload successful完了,真的提交了(我刚刚push的)让我们冷静下来,用撤消当前commit的方法先撤消本地的commit,这次我们来试试用hard参数来撤消$ git reset –hard head^HEAD is now at 3f22a06 [+]add file time.txt$ git statusOn branch masterYour 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 –forceTotal 0 (delta 0), reused 0 (delta 0)To github.com:pzqu/git_test.git + 3d113a7…3f22a06 master -> master (forced update)使用git reset –hard head^回滚到上一个commit使用git status查看现在的工作区情况,提示Your branch is behind ‘origin/master’ by 1 commit,代表成功表了上一次的提示状态,nothing to commit, working tree clean代表这次的修改全没了,清理的算是一个彻底。如果还想找回来怎么办,我们还真是有办法让你找回来的,见救命的后悔药。git push origin master –force 命令强制提交到远程仓库(注意,如果是在团队合作的情况下,不到迫不得已不要给命令加–force参数) 让我们看看githubupload successful真的撤消了远程仓库,长舒一口气。暂存区(Stage)到工作区(Working Directory)如果我们刚刚执行了git reset –soft或者add等的操作,把一些东西加到了我们的暂存区,比如日志文件,我们就要把他们从暂存区拿出来。$ git statusOn branch masterYour 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 statusOn branch masterYour branch is up to date with ‘origin/master’.Untracked files: (use “git add <file>…” to include in what will be committed) mysql.lognothing 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.txt10:41$ echo 18:51 > time.txt$ git statusOn branch masterYour 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.txtno changes added to commit (use “git add” and/or “git commit -a”)$ cat time.txt18:51$ git checkout – time.txt$ cat time.txt10:41更新time.txt的内容,可以status看到他发生了变化git checkout – time.txt , 取消这次在工作区的修改,如果他已经被add加到了暂存区,那么这个命令就没有用了,他的意思是取消本次在工作区的修改,去上一次保存的地方。如果没有add就回到和版本库一样的状态;如果已经加到了暂存区,又做了修改,那么就回加到暂存区后的状态将文件回滚到任意的版本我们这里说的把文件回滚到以前的某个版本的状态,完整的含义是保持其他文件的内容不变,改变这个文件到以前的某个版本,然后修改到自己满意的样子和做下一次的提交。核心命令git checkout [<options>] [<branch>] – <file>…我们还是用time.txt这个文件来做试验,先搞三个版本出来,在这里我已经搞好了,来看看:版本1,time.txt内容00:50commit 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:51commit 856a74084bbf9b678467b2615b6c1f6bd686ecffAuthor: pzqu <pzqu@example.com>Date: Sat Dec 22 19:39:19 2018 +0800 []update time to 18:51版本3,time.txt内容10:41commit 3f22a0639f8d79bd4e329442f181342465dbf0b6Author: 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.txt10:41$ git statusOn branch masterYour branch is up to date with ‘origin/master’.Changes to be committed: (use “git reset HEAD <file>…” to unstage) modified: time.txt使用checkout+commit id+– filename的组合,横跨版本2把历史版本3的time.txt搞出来了查看状态,time.txt被改变了我们来把time.txt恢复到版本1,同样的方法,因为版本1是上一次提交我们可以省略掉版本号$ git checkout – time.txt$ cat time.txt00:50看到了吧!只要用git checkout commit_id – filename的组合,想搞出哪个文件历史版本就搞出哪个。到了这里,你可能会很懵比,reset和checkout命令真的好像啊!都可以用来做撤消checkout语义上是把什么东西取出来,所以此命令用于从历史提交(或者暂存区域)中拷贝文件到工作目录,也可用于切换分支。reset语义上是重新设置,所以此命令把当前分支指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录。还想不通可以给我发邮件:pzqu@qq.com救命的后悔药来到这里我已经很清楚的你的现况了,你的代码丢了现在一定非常的着急,不要慌,总是有办法找回他们的。但是前提是要保证你的项目根目录下.git文件夹是完整的,要是手动删除了里面的一些东西那就真完了。还要保证一点,你的代码以前是有过git追踪的,最少add过找回你丢失的历史记录Git提供了一个命令git reflog用来记录你的每一次命令,贴个图吧直观点:upload successful有没有发现,git reflog里的全部都是和改变目录树有关的,比如commit rebase reset merge,也就是说一定要有改变目录树的操作才恢复的回来像add这种操作就不能恢复了吗?那肯定不是,只是要用更麻烦点的方式来恢复git log是一样的,也可以看到所有分支的历史提交,不一样的是看不到已经被删除的 commit 记录和 reset rebase merge 的操作 我们可以看到git reflog前面的就是commit id,现在我们就可以用之前介绍过的方法来回滚版本了,撤消当前commit$ git reset –hard 856a740HEAD is now at 856a740 []update time to 18:51$ git log -1commit 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 35b66edHEAD is now at 35b66ed []update time to 00:50$ git log -2commit 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:50commit 856a74084bbf9b678467b2615b6c1f6bd686ecffAuthor: pzqu <pzqu@example.com>Date: Sat Dec 22 19:39:19 2018 +0800 []update time to 18:51根据git 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 statusOn branch masterYour 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 35b66ed8HEAD is now at 35b66ed []update time to 00:50$ git statusOn branch masterYour branch is up to date with ‘origin/master’.nothing to commit, working tree clean$ lsREADME.md need_stash.txt share_file.txt time.txt创建一个叫lose_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-foundChecking object directories: 100% (256/256), done.Checking objects: 100% (3/3), done.dangling blob 7f5965523d2b9e850b39eb46e8e0f7c5755f6719dangling commit fdbb19cf4c5177003ea6610afd35cda117a41109dangling commit 8be46aa83f0fe90317b0c6b9c201ad994f8caeafdangling blob 11400c1d56142615deba941a7577d18f830f4d85dangling tree 3bd4c055afedc51df0326def49cf85af15994323dangling commit 3d113a773771c09b7c3bf34b9e974a697e04210adangling commit bfdc065df8adc44c8b69fa6826e75c5991e6cad0dangling tree c96ff73cb25b57ac49666a3e1e45e0abb8913296dangling blob d6d03143986adf15c806df227389947cf46bc6dedangling commit 7aa21bc382cdebe6371278d1af1041028b8a2b09这里涉及到git的一些低层的知识,我们可以看到这里有blob、commit、tree类型的数据,还有tag等类型的。他们是什么含义呢?upload successfulblob组件并不会对文件信息进行存储,而是对文件的内容进行记录commit组件在每次提交之后都会生成,当我们进行commit之后,首先会创建一个commit组件,之后把所有的文件信息创建一个tree组件,所以哪个blob代表什么文件都可以在tree 里找到 我们来看看怎么恢复刚刚不见了的lose_file.txt文件,在上面执行完git fsck –lost-found命令,返回的第一行blob我们看看他的内容git show 7f5965523d2b9e850b39eb46e8e0f7c5755f6719my lose messagegit show 7f5965523d2b9e850b39eb46e8e0f7c5755f6719 > lose_file.txt$ lsREADME.md lose_file.txt need_stash.txt share_file.txt time.txt看到没有,就是我们丢失的文件内容,这样就找回来了! 我们再来看看commit 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.txt用git 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 7f5965523d2b9e850b39eb46e8e0f7c5755f6719blob$ git cat-file -p 7f5965523d2b9e850b39eb46e8e0f7c5755f6719my lose message$ git cat-file -t b2484b5ab58c5cb6ecd92dacc09b41b78e9b0001tree$ git cat-file -p b2484b5ab58c5cb6ecd92dacc09b41b78e9b0001100644 blob f9894f4195f4854cfc3e3c55960200adebbc3ac5 README.md100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 need_stash.txt100644 blob 83f50ec84c00f5935da8089bac192171cfda8621 share_file.txt100644 blob f0664bd6a49e268d3db47c508b08d865bc25f7bb time.txt这里用find .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 -135b66ed8e3ae2c63cc4ebf323831e3b917d2b1d4 (HEAD -> master, origin/master, origin/HEAD) []update time to 00:50M time.txt$ git statusOn branch masterYour 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 -131cc2774f0668b5b7c049a404284b19e9b40dc5d (HEAD -> master) []update time to 00:50A lose_file.txtA test_amend.txtM time.txt查看文件提交日志只有time.txtstage里还有新的修改在使用git commit –amend –no-edit合并到上一个提交里,如果不加–no-edit参数的话,会提示你来修改commit提示信息(这个命令也可以用在重复编辑commit message)。查看日志,合并提交成功!tag标签创建一个tag标签是一个类似于快照的东西,常常用于测试和发布版本。所以我们常常把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 tagrelease_v1.0release_v1.1$ git push –tagsCounting 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.1切换到想打tag的分支创建名为release_v1.0带有信息release v1.0的tag创建的不带有tag的提交信息的release_v1.1git tag查看tag推送本地全部tag也可以推送单个tag$ git push origin release_v1.1Total 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.0Deleted tag ‘release_v1.0’ (was eb5d177)$ git push origin :refs/tags/release_v1.0To github.com:pzqu/git_test.git - [deleted] release_v1.0$ git tagrelease_v1.1本地删除名为release_v1.0的tag远程删除名为release_v1.0的tag对历史提交打tag先看看当前的log31cc277 (HEAD -> release_v1.0, tag: release_v1.1, origin/release_v1.0, master) []update time to 00:50856a740 []update time to 18:513f22a06 [+]add file time.txt4558a25 (origin/dev/pzqu, dev/pzqu) []test stashd9e018e []merge master to dev/pzqu比方说要对[]update time to 18:51这次提交打标签,它对应的commit id是856a740,敲入命令:$ git tag v.9 856a740$ git log –pretty=oneline –abbrev-commit31cc277 (HEAD -> release_v1.0, tag: release_v1.1, origin/release_v1.0, master) []update time to 00:50856a740 (tag: v0.9) []update time to 18:51成功打上git忽略不想提交的文件我们有两种情况,一种是我们根本就不想这些文件出现在git库里比如日志文件;另一种是git远程仓库里有这些文件,就像通用的配置文件,我们必须要在本地修改配置来适应运行环境,这种情况下我们不想每次提交的时候都去跟踪这些文件。忽略自动生成的垃圾文件、中间文件、敏感信息文件忽略文件的原则是:忽略操作系统自动生成的文件,比如缩略图等;忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。我们要怎么做呢?在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 文件名upload successful创建time.txt文件并写入10:41,提交到远程仓库使用命令git update-index —assume-unchanged加time.txt加到忽略名单里修改time.txt的内容为10:43status查看确实没有被跟踪 看远程仓库upload successful取消跟踪忽略核心命令:git update-index —no-assume-unchanged 文件名upload successfulpull同步远程仓库,真的没有更新刚刚被添加跟踪忽略的文件git update-index —no-assume-unchanged取消跟踪忽略status查看,出现文件的跟踪查看跟踪记录如果忘记了哪些文件被自己本地跟踪upload successful使用命令git update-index —assume-unchanged加time.txt加到忽略名单里使用git ls-files -v| grep ‘^h\ ‘命令可以看到小写h代表本地不跟踪的文件小结学完本文章,你将学会撤消commit,回滚暂存区,回滚工作区、回退远程仓库两种方法找回不小心丢失的文件提交的时候漏了文件,修改commit的提交信息tag操作,创建、创建有描述信息的tag、删除tag、删除远程tag、推送本地单个tag和全部taggit忽略自动生成的垃圾文件、中间文件、敏感信息文件;忽略远程存在,本地不想与远程同步的文件并恢复跟踪和查看哪些文件被跟踪注意事项理论上,git日常用到的命令是 diff show fetch rebase pull push checkout commit status 等,这些命令都不会导致代码丢失,假如害怕代码丢失,可以预先commit一次,再进行修改,但切记不可使用自己不熟悉的命令 任何命令,不要加上-f的强制参数,否则可能导致代码丢失建议多使用命令行,不要使用图形界面操作下集引用git官网廖雪峰的官方网站-git篇hexo博客部署到vps关于git reset –hard这个命令的惨痛教训Git 基础再学习之:git checkout – file如何理解git checkout – file和git reset HEAD – file此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号
...