本文由云+社区发表html
做者:工程师小熊mysql
摘要:上一集咱们一块儿入门学习了git的基本概念和git经常使用的操做,包括提交和同步代码、使用分支、出现代码冲突的解决办法、紧急保存现场和恢复现场的操做。学会之后已经足够咱们使用Git参加协做开发了,可是在开发的过程当中不免会出错,本文主要介绍版本控制的过程当中出错了的场景,以及Git开发的一些技巧,让咱们用的更流畅。git
上集回顾:github
本文核心:算法
若是你发现刚刚的操做一不当心commit了,所幸你尚未推送到远程仓库,你能够用reset
命令来撤消你的此次提交。sql
reset
命令的做用:重置HEAD(当前分支的版本顶端)到另一个commit。安全
咱们的撤消当前提交的时候每每不但愿咱们这次提交的代码发生任何丢失,只是撤消掉commit的操做,以便咱们继续修改文件。若是咱们是想直接不要了此次commit的所有内容的任何修改咱们将在下一小节讨论。hexo
来,咱们先说一句蠢话来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
复制代码
my 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.txt
复制代码
git 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^
回滚到上一个commit
git 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:41
复制代码
time.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.txt
复制代码
checkout
+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: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 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
复制代码
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-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.txt
复制代码
commit tree
的内容git cat-file -p
能够看到commit的内容,能够选择把这个commit合并到咱们的分支里,仍是reset merge rebase cherry-pick
这些命令来合commit
git 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.txt
复制代码
find .git/objects -type f | xargs ls -lt | sed 3q
返回了近3个修改的文件,想要更多就改3q
这个数值,好比你想输出100个就用100q
git 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.txt
复制代码
time.txt
git 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.1
复制代码
tag
的分支release_v1.0
带有信息release v1.0
的tag
tag
的提交信息的release_v1.1
git tag
查看tag
tag
也能够推送单个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
复制代码
release_v1.0
的tag
release_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就会自动忽略这些文件。
touch test.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:43
status
查看确实没有被跟踪 看远程仓库核心命令:
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的强制参数,不然可能致使代码丢失
建议多使用命令行,不要使用图形界面操做
Git 基础再学习之:git checkout -- file
如何理解git checkout -- file和git reset HEAD -- file
此文已由腾讯云+社区在各渠道发布
获取更多新鲜技术干货,能够关注咱们腾讯云技术社区-云加社区官方号及知乎机构号