本文由云 + 社区发表

作者:工程师小熊

摘要:上一集我们一起入门学习了 git 的基本概念和 git 常用的操作,包括提交和同步代码、使用分支、出现代码冲突的解决办法、紧急保存现场和恢复现场的操作。学会以后已经足够我们使用 Git 参加协作开发了,但是在开发的过程中难免会出错,本文主要介绍版本控制的过程中出错了的场景,以及 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 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.txt

好了,刚刚我们 “不小心” 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.txt

好了,有惊无险,这就是撤消 commit 的操作。另一种情况是如果你想撤消 commit 的时候支持舍弃这次全部的修改就把git reset --soft head^改成git reset --hard head^,这样你本地修改就彻底丢掉了 (慎用),如果真用了想找回来怎么办?见救命的后悔药。

当然了,你只要开心不加softhard参数也是安全的 (相当于使用了--mixed参数),只不过是撤消以后你的本次修改就会回到add之前的状态,你可以重新检视然后再做修改和commit

回退远程仓库

要是我们做的更过分一点,直接把这次commit直接给push怎么办?要是被发现就全完了,我们来看看 github 上的远程仓库。

imgupload successful

完了,真的提交了(我刚刚 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)

imgupload successful

真的撤消了远程仓库,长舒一口气。

暂存区(Stage)到工作区(Working Directory)

如果我们刚刚执行了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)

回滚文件到某个提交

当我们想要把某个文件任意的回滚到某次提交上,而不改变其他文件的状态我们要怎么做呢?

我们有两种情况,一种是,只是想在工作区有修改的文件,直接丢弃掉他现在的修改;第二种是想把这个文件回滚到以前的某一次提交。我们先来说第一种:

取消文件在工作区的修改

$ 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:41

核心命令

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.txt

我们来把 time.txt 恢复到版本 1,同样的方法,因为版本 1 是上一次提交我们可以省略掉版本号

$ git checkout -- time.txt

$ cat time.txt
00:50

看到了吧!只要用git checkout commit_id -- filename的组合,想搞出哪个文件历史版本就搞出哪个。

到了这里,你可能会很懵比,resetcheckout命令真的好像啊!都可以用来做撤消

还想不通可以给我发邮件:pzqu@qq.com

救命的后悔药

来到这里我已经很清楚的你的现况了,你的代码丢了现在一定非常的着急,不要慌,总是有办法找回他们的。但是前提是要保证你的项目根目录下.git 文件夹是完整的,要是手动删除了里面的一些东西那就真完了。还要保证一点,你的代码以前是有过 git 追踪的,最少add

找回你丢失的历史记录

Git 提供了一个命令git reflog用来记录你的每一次命令,贴个图吧直观点:

imgupload successful

$ 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:51

找回忘记提交的历史记录

你之前没有 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.txt

核心命令: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等类型的。他们是什么含义呢?

imgupload successful

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.txt

后记:

如果你发现执行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.txt

漏提交

有时候会碰到我们已经 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.txt

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 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.1

也可以推送单个 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.1

对历史提交打 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 远程仓库里有这些文件,就像通用的配置文件,我们必须要在本地修改配置来适应运行环境,这种情况下我们不想每次提交的时候都去跟踪这些文件。

忽略自动生成的垃圾文件、中间文件、敏感信息文件

忽略文件的原则是:

  1. 忽略操作系统自动生成的文件,比如缩略图等;
  2. 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如 Java 编译产生的.class 文件;
  3. 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。

我们要怎么做呢?

在 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.logtest2.log * status查看,真是工作区是clean,新创建的文件没有被跟踪

忽略远程存在,本地不想与远程同步的文件

添加跟踪忽略

核心命令:

git update-index assume-unchanged 文件名

imgupload successful

imgupload successful

取消跟踪忽略

核心命令:

git update-index no-assume-unchanged 文件名

imgupload successful

查看跟踪记录

如果忘记了哪些文件被自己本地跟踪

imgupload successful

小结

学完本文章,你将学会

注意事项

理论上,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

此文已由腾讯云 + 社区在各渠道发布

获取更多新鲜技术干货,可以关注我们腾讯云技术社区 - 云加社区官方号及知乎机构号


↙↙↙阅读原文可查看相关链接,并与作者交流