版本控制工具——Git 常用操作



转载请注明 AIQ - 最专业的机器学习大数据社区  http://www.6aiq.com

AIQ 机器学习大数据 知乎专栏 点击关注

本文由云 + 社区发表

作者:工程师小熊

版本控制工具——Git 常用操作(上)

摘要:用了很久的 Git 和 svn, 由于总是眼高手低,没能静下心来写这些程序员日常开发最常用的知识点。现在准备开一个专题,专门来总结一下版本控制工具,让我们从 git 开始。完成本系列博客的阅读以后,你将掌握 git 的基本概念与 git 的基本命令,可以在本地随心所欲的完成代码的提交撤销保存修改等操作、可以流畅的参与多人协作,本文致力于快速的入门,如果涉及到更高级的功能需要进行更深一步的学习。

本文核心点:

  • Git 的基本概念
  • 一个人使用 Git 时的代码版本控制–(提交、拉代码、分支操作)
  • 多人合作时的代码版本控制–(合并冲突、暂存代码)

什么是 Git

简介

git 是世界上目前最先进的分布式版本控制系统, 致力于团队、个人进行项目版本管理,完美的解决难以比较代码、难以合并代码、难以取消修改、难以在写当前代码的过程中保存未完成的修改去修改线上版本的 bug 等的痛点。

git 是一个非常强大的工具,但作为一个 git 使用者来说,不用完全学习 Git 的知识点与命令,因为有的命令的使用频率非常的低甚至数年都不会用到,让我们来由浅入深进行学习。

git 的历史

git 是 linux 的创始人 linus,在付费版本控制工具 BitMover 收回对 Linux 社区免费使用权利的时候,一怒之下花费两个星期的时间写出来的。(牛笔的人)

开始
==

安装 git

选择自己的操作系统对应的 git 版本安装,安装成功后运行git version后,输出 git 版本则安装正确。

git 官方:https://git-scm.com/downloads

配置用户信息

使用git config命令来配置用户名和邮箱

git config --global user.name "pzqu" 
git config --global user.email pzqu@example.com

如果用了 –global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 –global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。

使用git config user.namegit config user.email来检查是否成功,也可以直接用git config --list来列出全部 git 配置信息来查看

0e5d9c96254c46408cef53f870300f5b.png

创建 git 托管的项目

假如我们创建一个项目叫 make_money,先创建一个文件夹叫 make_money,再使用git init命令创建 git 项目。

# pzqu @ pzqu-pc in ~/Documents/code/test [0:05:29]
$ mkdir make_money

# pzqu @ pzqu-pc in ~/Documents/code/test [0:06:24]
$ ls
make_money

# pzqu @ pzqu-pc in ~/Documents/code/test [0:06:29]
$ cd make_money

# pzqu @ pzqu-pc in ~/Documents/code/test/make_money [0:07:10]
$ git init
Initialized empty Git repository in /Users/pzqu/Documents/code/test/make_money/.git/

# pzqu @ pzqu-pc in ~/Documents/code/test/make_money on git:master o [0:07:12]
$ ls -al
total 0
drwxr-xr-x  3 pzqu  staff   96 11  7 00:07 .
drwxr-xr-x  3 pzqu  staff   96 11  7 00:06 ..
drwxr-xr-x  9 pzqu  staff  288 11  7 00:07 .git

创建成功以后,会出现一个叫.git 的隐藏文件夹,这个就是你的 git 仓库,以后所有的 git 操作历史提交记录信息就全部记录在此了,只要这个文件夹在就可以记住我们的全部 git 操作

工作区和暂存区

在使用 git 的时候还要清楚暂存区和工作区的含义,参考廖雪峰的官方网站 -git 篇 - 工作区和暂存区

常见情况

提交代码

新文件与修改

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [11:37:50]
$ ls
README.md

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [11:42:02]
$ touch file1.txt

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [11:42:15]
$ git add file1.txt

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [11:42:23]
$ 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:   file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [11:56:38]
$ git commit -m "[+]add new file1.txt"
[master 66cc488] [+]add new file1.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file1.txt

上图操作包含:

  • 创建新文件 file1.txt
  • add 添加修改的内容到索引
  • status 查看修改的内容
  • commit 把索引提交到本地分支

git add .:监控工作区的状态树,此命令会把工作时的所有变化提交到暂存区,包括文件内容修改 (modified) 以及新文件(new),但不包括被删除的文件。

git add -u:他仅监控已经被 add 的文件(即 tracked file),他会将被修改的文件提交到暂存区。add -u 不会提交新文件(untracked file)。(git add –update 的缩写)

git add -A:是上面两个功能的合集(git add –all 的缩写)

a1aaa90365ae4207962ee644a22d6677.png

git show 列出最近一次的提交

对于 commit:像这样,你不断对文件进行修改,然后不断提交修改到版本库里,就好比玩 RPG 游戏时,每通过一关就会自动把游戏状态存盘,如果某一关没过去,你还可以选择读取前一关的状态。有些时候,在打 Boss 之前,你会手动存盘,以便万一打 Boss 失败了,可以从最近的地方重新开始。Git 也是一样,每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在 Git 中被称为 commit。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个 commit 恢复,然后继续工作,而不是把几个月的工作成果全部丢失。

删除文件

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [12:55:24]
$ ls
README.md file1.txt

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [12:55:25]
$ git rm file1.txt
rm 'file1.txt'

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [12:55:30]
$ ls
README.md

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [12:55:32]
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    deleted:    file1.txt

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [12:55:40] C:128
$ git commit -m "[-]delete file1.txt"
[master e278392] [-]delete file1.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 file1.txt

上图操作包含:

  • 创建新文件 file1.txt
  • git rm 删除 file1.txt 文件
  • status 查看修改的内容
  • commit 把索引提交到本地分支

tip1: 如果没有用 git rm 删除文件,在本地删除文件后,git add 一下再提交可以达到同样的效果

tip2: 要是你加班太晚,头晕不小心删除了不想删除的文件怎么办?见

下一篇:版本控制工具——Git 常用操作(下)- 后悔药

拉代码

方法一 pull

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [17:01:13]
$ git pull
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:pzqu/git_test
   5fd4d8f..7b54a8a  master     -> origin/master
Merge made by the 'recursive' strategy.
 share_file.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 share_file.txt

上图命令:

  • git pull

查看本地仓库变化git log

4dd795c5f4634bf490b51b3081adbf32.png

上图可以看到向远程仓库 pull 的时候,出现了两个新的 commit,commit 7b54a8ae74...的提交信息为Create share_file.txt, 另一个commit fdbb19cf4c51770的提交信息为Merge branch 'master' of github.com:pzqu/git_test。事实上主线只有一个提交,为什么会出现这种情况? 是因为 pull 其实会做两个操作

  • 拉远程仓库代码到本地
  • 自动与当前分支合并并生成一个合并成功的提交

注意这里的第二个个步骤如果远程有人和你改了同一个文件就会出现一个冲突,这个时候 git 会提示你哪些文件有冲突,手动改了再提交一次就可以了。详情见合并冲突

方法二 fetch

我在远程修改了文件,向share_file.txt加了一行内容tom modify,此时拉代码。

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [21:07:21]
$ git fetch

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [21:08:43]
$ git rebase origin/master
First, rewinding head to replay your work on top of it...
Applying: [+]add new file1.txt
Applying: [-]delete file1.txt

上图所示有以下两个操作

  • fetch 拉取远端代码到本地
  • rebase 把本地代码提交基于远端分支重新 replay

效果如下:

0457c1174ecd40418c9af51c216a84fe.png

上图是git log所输出的提交内容,刚刚 pull 的时候忘记把 pull 自动产生的 merge 提交到远程,rebase 的时候把本地的提交放到了远程提交之后,看起来就是一条直线,比较优雅,也是推荐的方式。

同样的,如果产生了冲突,详情见合并冲突

分支操作

创建分支

分支是多人协同最经典的地方所在,我们来创建一个分支

$ git checkout -b dev/pzqu origin/master
Branch 'dev/pzqu' set up to track remote branch 'master' from 'origin'.
Switched to a new branch 'dev/pzqu'

$ git branch
* dev/pzqu
  master
  • git checkout -b 分支名 其他分支,-b代表创建并切换到新建的分支,分支名代表新创建的分支叫什么名字,这里叫dev/pzqu其他分支代表基于哪一个分支来创建,这里基于远程的 master 分支origin/master,如果省略则代表基于当前分支
  • git branch展示本地的分支情况,加-a参数可以展示全部的分支,包括远程分支
  • *在分支前,指明了现在所在的分支是dev/pzqu

切换分支

$ git checkout -b dev/pzqu2
Switched to a new branch 'dev/pzqu2'

$ git branch
  dev/pzqu
* dev/pzqu2
  master

$ git checkout dev/pzqu
Switched to branch 'dev/pzqu'
Your branch is up to date with 'origin/master'.

$ git branch
* dev/pzqu
  dev/pzqu2
  master
  • 基于当前分支创建了一个新的分支并自动切换过去dev/pzqu2
  • git checkout 已存在的分支名切换分支回到dev/pzqu

删除分支

$ git branch
* dev/pzqu
  dev/pzqu2
  master
  
$ git branch -D dev/pzqu2
Deleted branch dev/pzqu2 (was 7c9be37).

$ git branch
* dev/pzqu
  master 
  • 位于dev/pzqu,删除了dev/pzqu2分支

合并冲突

合并同一个分支的冲突(常见)

为了产生一个冲突,我在另一个地方向远程仓库提交了代码,更改share_file.txt文件,加了一行内容tom add for merge

本地修改同一个文件加了一行pzqu add for merge,并提交到本地,这样一来,本地和远程仓库的同一个文件就不一样了,一会拉代码一定会产生一个冲突。效果如下:

aca8ae93b8e34241802c9aeb856edc1a.png

  • 一般 rebase 或 pull 冲突的时候,都会出现提示,然后 git status 会出现上图图示
  • 这个时候不可以进行任何分支切换和 commit 操作,按照他提示进行处理
  • git status 提示哪个文件是都被修改的,both modified,然后使用编辑器修改该文件,解决冲突
  • 解决完成后,git add 添加该冲突文件
  • git rebase –continue,并更新 commit message,完成整个 rebase 流程 我们来看看这个冲突的文件:

5ef85397133345bab966e346ebf36bad.png

Git 用<<<<<<<=======>>>>>>>标记出不同分支的内容,我们修改如下后保存:

imgupload successful

git addgit rebase --continue后完成 rebase,效果如下,再push的远程仓库即可

6feae65e88ca4109ade14bb633f423bb.png

合并不同分支的代码产生冲突

关于怎么创建分支与切换分支见创建分支和切换分支, 这里只讨论合并时产生的冲突的情况,我们已经基于master分支创建了一个dev/pzqu分支

$ git branch
* dev/pzqu
  master

切换到master分支,加一行master add for merge并提交,文件内容如下:

$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
master add for merge

切换到dev/pzqu分支,向share_file.txt加入一行dev/pzqu add for merge并提交,现在share_file.txt内容如下:

$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
dev/pzqu add for merge

现在两个分支的同一个文件内容不一样了,现在我们在dev/pzqu分支上进行合并:

$ git merge master
Auto-merging share_file.txt
CONFLICT (content): Merge conflict in share_file.txt
Automatic merge failed; fix conflicts and then commit the result.

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:dev/pzqu x [11:17:31] C:1
$ git status
On branch dev/pzqu
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:   share_file.txt

no changes added to commit (use "git add" and/or "git commit -a")

$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
<<<<<<< HEAD
dev/pzqu add for merge
=======
master add for merge
>>>>>>> master

上图出现了一个冲突,是我们意料之中的,修改share_file.txt文件,解决此冲突:

$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
dev/pzqu add for merge
master add for merge

$ git add share_file.txt

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:dev/pzqu x [11:22:40]
$ git commit -m "[*]merge master to dev/pzqu"
[dev/pzqu d9e018e] [*]merge master to dev/pzqu

# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:dev/pzqu o [11:23:00]
$ git status
On branch dev/pzqu
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

冲突解决也提交了,看看我们现在的分支内容:

693d355e43bd46da8580fcd55846a638.png

上图我们可以看到:

  • master分支比远程origin/master分支多一次提交,dev/pzqu分支由于是基于origin/master分支,合并了master分支的提交和当前dev/pzqu分支的提交,超出本地master两个提交,致此我们把master合并到dev/pzqu的操作就完成了。
  • 通常我们开一个新的开发分支是为了在自己的分支上写代码,方便提交也不会把主线弄乱,现在我们用同样的方法将dev/pzqu合并到master分支,然后把两个分支都提交到远程。
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

$ git merge dev/pzqu
Updating 58f047a..d9e018e
Fast-forward
 share_file.txt | 1 +
 1 file changed, 1 insertion(+)

$ git push origin master
Total 0 (delta 0), reused 0 (delta 0)
To github.com:pzqu/git_test.git
   7c9be37..d9e018e  master -> master
   
$ git push origin dev/pzqu
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 887 bytes | 887.00 KiB/s, done.
Total 9 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
remote:
remote: Create a pull request for 'dev/pzqu' on GitHub by visiting:
remote:      https://github.com/pzqu/git_test/pull/new/dev/pzqu
remote:
To github.com:pzqu/git_test.git
 * [new branch]      dev/pzqu -> dev/pzqu
  • 切换到master分支
  • 合并dev/pzqumaster分支
  • master推到远程仓库
  • 如果dev/pzqu要保留,就可以推送到远程仓库。

cd088a439006449ca5ae39f688215f97.png

  • 现在我们可以看到全部的分支都在一起了,强迫症都舒服了。

暂存代码保存现场

这种情况一般是出现在你正在完成一个功能,但是忽然线上发现了一个 Bug,必须马上开一个新的分支来修复 bug,但是现在的功能没写完不打算提交 (commit),现在怎么办??不用怕暂存代码来帮助你。

$ git status
On branch dev/pzqu
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   need_stash.txt
    modified:   share_file.txt

$ git stash
Saved working directory and index state WIP on dev/pzqu: d9e018e [*]merge master to dev/pzqu

$ git stash list
stash@{0}: WIP on dev/pzqu: d9e018e [*]merge master to dev/pzqu

$ git status
On branch dev/pzqu
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

//省略操作:去创建一个Bug分支,修复他并完成与主线的合并,删除Bug分支。
//省略操作:切回来当前分支继续开发
//下面来恢复现场

$ git stash apply stash@{0}
On branch dev/pzqu
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   need_stash.txt

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:   share_file.txt
  • status查看到有 2 个文件修改没有提交
  • stash把修改放到暂存区,并生成一个 id
  • stash list列出暂存区所有内容
  • stash apply重新把暂存区内容放到本地

这里的stash apply成功的把暂存区的一次暂存恢复到了本地,但是暂存区还有会保存这次暂存,如果想删除这次暂存要用git stash drop来删除;也可以用git stash pop,恢复最后一次暂存的同时把 stash 内容也删了。

$ git stash drop stash@{0}
Dropped stash@{0} (bfdc065df8adc44c8b69fa6826e75c5991e6cad0)

$ git stash list

好了,暂存区清干净了。

​ 注意:要放到暂存区的文件一定要先通过 git add 加到 index

小结
==

本文阅读结束以后,我们学会了

  • Git 的基本概念,知道 git 的作用、历史;学会安装配置 Git,使用 Git 创建项目托管以及工作区和暂存区的概念
  • 学会 Git 的本地操作,提交、拉代码、创建切换删除分支操作,
  • 多人合作时的代码版本控制,学会了不同情况下的合并冲突、暂存代码操作

下集预告

Git 常用操作(下)我计划给大家介绍以下点:

  • 后悔药 - 各种后悔操作(撤消 commit, 回滚,回退远程仓库等)
  • 哎呀,提交的时候漏了文件
  • tag 操作
  • git 忽略不想提交的文件

注意事项

理论上,git 日常用到的命令是 diff show fetch rebase pull push checkout commit status 等,这些命令都不会导致代码丢失,假如害怕代码丢失,可以预先 commit 一次,再进行修改,但切记

不可使用自己不熟悉的命令 任何命令,不要加上 -f 的强制参数,否则可能导致代码丢失

建议多使用命令行,不要使用图形界面操作

版本控制工具——Git 常用操作(下)

本文由云 + 社区发表

作者:工程师小熊

摘要:上一集我们一起入门学习了 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 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
  • 创建 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^,这样你本地修改就彻底丢掉了 (慎用),如果真用了想找回来怎么办?见救命的后悔药。

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

回退远程仓库

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

c8e71b1c5f274deea586e514687a3013.png

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

e5428aaddf964764b3e7318502bf4655.png

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

暂存区(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)
  • status查看暂存区,里面有一个 mysql.log 被放进去了
  • git reset -- mysql.logmysql.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 被改变了

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

$ git checkout -- time.txt

$ cat time.txt
00:50

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

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

  • checkout语义上是把什么东西取出来,所以此命令用于从历史提交(或者暂存区域)中拷贝文件到工作目录,也可用于切换分支。
  • reset语义上是重新设置,所以此命令把当前分支指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录。

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

救命的后悔药

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

找回你丢失的历史记录

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

73abfb6b02e64e67aa6a61029cf3e228.png

  • 有没有发现,git reflog里的全部都是和改变目录树有关的,比如commit rebase reset merge,也就是说一定要有改变目录树的操作才恢复的回来
  • 像 add 这种操作就不能恢复了吗?那肯定不是,只是要用更麻烦点的方式来恢复
  • 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)版本。
  • 我们用statusls再看,这个叫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等类型的。他们是什么含义呢?

43b0f406e328422e9dafc33e69dfa195.png

  • 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 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这些命令来合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
  • stage 里还有新的修改在
  • 使用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 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.0tag
  • 创建的不带有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.0tag
  • 远程删除名为release_v1.0tag

对历史提交打 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 文件名

17a935a2a21448f8a83ba2debf215cc8.png

  • 创建time.txt文件并写入10:41, 提交到远程仓库
  • 使用命令git update-index —assume-unchangedtime.txt加到忽略名单里
  • 修改time.txt的内容为10:43
  • status查看确实没有被跟踪 看远程仓库

ee286ad95b5a4800b4099c1d5228609e.png

取消跟踪忽略

核心命令:

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

b549d370962b42819d093a015d241b26.png

  • pull同步远程仓库,真的没有更新刚刚被添加跟踪忽略的文件
  • git update-index —no-assume-unchanged取消跟踪忽略
  • status查看,出现文件的跟踪

查看跟踪记录

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

bf3761c62ba0422cb73030af6446c6b5.png

  • 使用命令git update-index —assume-unchangedtime.txt加到忽略名单里
  • 使用git ls-files -v| grep '^h\ '命令可以看到小写 h 代表本地不跟踪的文件

小结
==

学完本文章,你将学会

  • 撤消 commit, 回滚暂存区,回滚工作区、回退远程仓库
  • 两种方法找回不小心丢失的文件
  • 提交的时候漏了文件,修改 commit 的提交信息
  • tag 操作,创建、创建有描述信息的 tag、删除 tag、删除远程 tag、推送本地单个 tag 和全部 tag
  • git 忽略自动生成的垃圾文件、中间文件、敏感信息文件;忽略远程存在,本地不想与远程同步的文件并恢复跟踪和查看哪些文件被跟踪

注意事项

理论上,git 日常用到的命令是 diff show fetch rebase pull push checkout commit status 等,这些命令都不会导致代码丢失,假如害怕代码丢失,可以预先 commit 一次,再进行修改,但切记

不可使用自己不熟悉的命令 任何命令,不要加上 -f 的强制参数,否则可能导致代码丢失

建议多使用命令行,不要使用图形界面操作


更多高质资源 尽在AIQ 机器学习大数据 知乎专栏 点击关注

转载请注明 AIQ - 最专业的机器学习大数据社区  http://www.6aiq.com