*****
**版本控制**
[TOC=6]
# 3. 版本操作
## 3.1 時光機穿梭
我們已經成功地添加并提交了一個readme.txt文件,現在,是時候繼續工作了,于是,我們繼續修改readme.txt文件,改成如下內容:
~~~
1612B 班 大家好嗎?
非常好!
那好,我們來學習git添加文件。
學習時光機的使用。
~~~
現在,運行`git status`命令看看結果:
~~~
$ git status
On branch 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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
~~~
`git status`命令可以讓我們時刻掌握倉庫當前的狀態,上面的命令輸出告訴我們,`readme.txt`被修改過了,但還沒有準備提交的修改。
雖然Git告訴我們`readme.txt`被修改了,但如果能看看具體修改了什么內容,自然是很好的。比如你休假兩周從國外回來,第一天上班時,已經記不清上次怎么修改的`readme.txt`,所以,需要用`git diff`這個命令看看:
~~~
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index c0c9a69..78d8036 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,3 +1,4 @@
1612B 班 大家好嗎?
非常好!
那好,我們來學習git添加文件。
+學習時光機的使用。
~~~
`git diff`顧名思義就是查看difference,顯示的格式正是Unix通用的diff格式,可以從上面的命令輸出看到,我們在最后一行添加了`學習時光機的使用。`一句話。
知道了對`readme.txt`作了什么修改后,再把它提交到倉庫就放心多了,提交修改和提交新文件是一樣的兩步,第一步是`git add`
~~~
$ git add readme.txt
~~~
同樣沒有任何輸出。在執行第二步`git commit`之前,我們再運行`git status`看看當前倉庫的狀態:
~~~
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
~~~
`git status`告訴我們,將要被提交的修改包括`readme.txt`,下一步,就可以放心地提交了:
~~~
$ git commit -m "add content"
[master 5d25890] add content
1 file changed, 1 insertion(+)
~~~
提交后,我們再用`git status`命令看看倉庫的當前狀態:
~~~
$ git status
On branch master
nothing to commit, working tree clean
~~~
Git告訴我們當前沒有需要提交的修改,而且,工作目錄是干凈(working tree clean)的。
## 3.2 小結
* 要隨時掌握工作區的狀態,使用`git status`命令。
* 如果`git status`告訴你有文件被修改過,用`git diff`可以查看修改內容。
## 3.3 版本回退
現在,你已經學會了修改文件,然后把修改提交到Git版本庫,現在,再練習一次,修改readme.txt文件如下:
~~~
1612B 班 大家好嗎?
非常好!
那好,我們來學習git添加文件。
學習時光機的使用。
學習版本回退。
~~~
然后嘗試提交:
~~~
$ git add readme.txt
$ git commit -m "add content2"
[master 513da34] add content2
1 file changed, 1 insertion(+)
~~~
像這樣,你不斷對文件進行修改,然后不斷提交修改到版本庫里,就好比玩RPG游戲時,每通過一關就會自動把游戲狀態存盤,如果某一關沒過去,你還可以選擇讀取前一關的狀態。有些時候,在打Boss之前,你會手動存盤,以便萬一打Boss失敗了,可以從最近的地方重新開始。Git也是一樣,每當你覺得文件修改到一定程度的時候,就可以“保存一個快照”,這個快照在Git中被稱為`commit`。一旦你把文件改亂了,或者誤刪了文件,還可以從最近的一個`commit`恢復,然后繼續工作,而不是把幾個月的工作成果全部丟失。
現在,我們回顧一下`readme.txt`文件一共有幾個版本被提交到Git倉庫里了:
版本1:wirte a readme file
~~~
1612B 班 大家好嗎?
非常好!
那好,我們來學習git添加文件。
~~~
版本2:add content
~~~
1612B 班 大家好嗎?
非常好!
那好,我們來學習git添加文件。
學習時光機的使用。
~~~
版本3:add content2
~~~
1612B 班 大家好嗎?
非常好!
那好,我們來學習git添加文件。
學習時光機的使用。
學習版本回退。
~~~
當然了,在實際工作中,我們腦子里怎么可能記得一個幾千行的文件每次都改了什么內容,不然要版本控制系統干什么。版本控制系統肯定有某個命令可以告訴我們歷史記錄,在Git中,我們用`git log`命令查看:
~~~
$ git log
commit 513da34713063be978f959507df8b17c62acdf6c (HEAD -> master)
Author: zhaoliang <2668645098@qq.com>
Date: Thu May 9 10:11:47 2019 +0800
add content2
commit 5d25890b8323df20f8a30a50701a51b2bbeb4856
Author: zhaoliang <2668645098@qq.com>
Date: Thu May 9 10:03:47 2019 +0800
add content
commit 30c0ddf65b656122d5383c86832b1e3ef7ee746c
Author: zhaoliang <2668645098@qq.com>
Date: Thu May 9 09:40:33 2019 +0800
wirte a readme file
~~~
`git log`命令顯示從最近到最遠的提交日志,我們可以看到3次提交,最近的一次是`add content2`,上一次是`add content`,最早的一次是`write a readme file`。
如果嫌輸出信息太多,看得眼花繚亂的,可以試試加上`--pretty=oneline`參數:
~~~
$ git log --pretty=oneline
513da34713063be978f959507df8b17c62acdf6c (HEAD -> master) add content2
5d25890b8323df20f8a30a50701a51b2bbeb4856 add content
30c0ddf65b656122d5383c86832b1e3ef7ee746c wirte a readme file
~~~
需要友情提示的是,你看到的一大串類似`513da3...`的是`commit id`(版本號),和SVN不一樣,Git的`commit id`不是1,2,3……遞增的數字,而是一個SHA1計算出來的一個非常大的數字,用十六進制表示,而且你看到的`commit id`和我的肯定不一樣,以你自己的為準。為什么`commit id`需要用這么一大串數字表示呢?因為Git是分布式的版本控制系統,后面我們還要研究多人在同一個版本庫里工作,如果大家都用1,2,3……作為版本號,那肯定就沖突了。
每提交一個新版本,實際上Git就會把它們自動串成一條時間線。如果使用可視化工具查看Git歷史,就可以更清楚地看到提交歷史的時間線:

好了,現在我們啟動時光穿梭機,準備把`readme.txt`回退到上一個版本,也就是`add content`的那個版本,怎么做呢?
首先,Git必須知道當前版本是哪個版本,在Git中,用`HEAD`表示當前版本,也就是最新的提交`513da3...`(注意我的提交ID和你的肯定不一樣),上一個版本就是`HEAD^`,上上一個版本就是`HEAD^^`,當然往上100個版本寫100個`^`比較容易數不過來,所以寫成`HEAD~100`。
現在,我們要把當前版本` add content2`回退到上一個版本`add content`,就可以使用`git reset`命令:
~~~
$ git reset --hard HEAD^
HEAD is now at 5d25890 add content
~~~
`--hard`參數有啥意義?這個后面再講,現在你先放心使用。
看看`readme.txt`的內容是不是版本`add content`:
~~~
$ cat readme.txt
1612B 班 大家好嗎?
非常好!
那好,我們來學習git添加文件。
學習時光機的使用。
~~~
果然被還原了。
還可以繼續回退到上一個版本`write a readme file`,不過且慢,然我們用`git log`再看看現在版本庫的狀態:
~~~
$ git log
commit 5d25890b8323df20f8a30a50701a51b2bbeb4856 (HEAD -> master)
Author: zhaoliang <2668645098@qq.com>
Date: Thu May 9 10:03:47 2019 +0800
add content
commit 30c0ddf65b656122d5383c86832b1e3ef7ee746c
Author: zhaoliang <2668645098@qq.com>
Date: Thu May 9 09:40:33 2019 +0800
wirte a readme file
~~~
最新的那個版本`add content2`已經看不到了!好比你從21世紀坐時光穿梭機來到了19世紀,想再回去已經回不去了,腫么辦?
辦法其實還是有的,只要上面的命令行窗口還沒有被關掉,你就可以順著往上找啊找啊,找到那個`add content2`的`commit id`是`513da3...`,于是就可以指定回到未來的某個版本:
~~~
$ git reset --hard 513da3
HEAD is now at 513da34 add content2
~~~
版本號沒必要寫全,前幾位就可以了,Git會自動去找。當然也不能只寫前一兩位,因為Git可能會找到多個版本號,就無法確定是哪一個了。
再小心翼翼地看看`readme.txt`的內容:
~~~
$ cat readme.txt
1612B 班 大家好嗎?
非常好!
那好,我們來學習git添加文件。
學習時光機的使用。
學習版本回退。
~~~
果然,我胡漢三又回來了。
Git的版本回退速度非常快,因為Git在內部有個指向當前版本的`HEAD`指針,當你回退版本的時候,Git僅僅是把HEAD從指向`add content2`:
~~~ascii
┌────┐
│HEAD│
└────┘
│
└──> ○ add content2
│
○ add content
│
○ write a readme file
~~~
改為指向`add content`:
~~~ascii
┌────┐
│HEAD│
└────┘
│
│ ○ add content2
│ │
└──> ○ add content
│
○ write a readme file
~~~
然后順便把工作區的文件更新了。所以你讓`HEAD`指向哪個版本號,你就把當前版本定位在哪。
現在,你回退到了某個版本,關掉了電腦,第二天早上就后悔了,想恢復到新版本怎么辦?找不到新版本的`commit id`怎么辦?
在Git中,總是有后悔藥可以吃的。當你用`$ git reset --hard HEAD^`回退到`add content`版本時,再想恢復到`add content2`,就必須找到`add content2`的commit id。Git提供了一個命令`git reflog`用來記錄你的每一次命令:
~~~
$ git reflog
513da34 (HEAD -> master) HEAD@{0}: reset: moving to 513da
5d25890 HEAD@{1}: reset: moving to HEAD^
513da34 (HEAD -> master) HEAD@{2}: commit: add content2
5d25890 HEAD@{3}: commit: add content
30c0ddf HEAD@{4}: commit (initial): wirte a readme file
~~~
終于舒了口氣,從輸出可知,`add content2`的commit id是`513da34`,現在,你又可以乘坐時光機回到未來了。
## 3.4 小結
現在總結一下:
* `HEAD`指向的版本就是當前版本,因此,Git允許我們在版本的歷史之間穿梭,使用命令`git reset --hard commit_id`。
* 穿梭前,用`git log`可以查看提交歷史,以便確定要回退到哪個版本。
* 要重返未來,用`git reflog`查看命令歷史,以便確定要回到未來的哪個版本。
## 3.5 工作區和暫存區
Git和其他版本控制系統如SVN的一個不同之處就是有暫存區的概念。
先來看名詞解釋。
### 工作區(Working Directory)
就是你在電腦里能看到的目錄,比如我的`shopcode`文件夾就是一個工作區:

### 版本庫(Repository)
工作區有一個隱藏目錄`.git`,這個不算工作區,而是Git的版本庫。
Git的版本庫里存了很多東西,其中最重要的就是稱為stage(或者叫index)的暫存區,還有Git為我們自動創建的第一個分支`master`,以及指向`master`的一個指針叫`HEAD`。

分支和`HEAD`的概念我們后面再講。
前面講了我們把文件往Git版本庫里添加的時候,是分兩步執行的:
第一步是用`git add`把文件添加進去,實際上就是把文件修改添加到暫存區;
第二步是用`git commit`提交更改,實際上就是把暫存區的所有內容提交到當前分支。
因為我們創建Git版本庫時,Git自動為我們創建了唯一一個`master`分支,所以,現在,`git commit`就是往`master`分支上提交更改。
你可以簡單理解為,需要提交的文件修改通通放到暫存區,然后,一次性提交暫存區的所有修改。
俗話說,實踐出真知。現在,我們再練習一遍,先對`readme.txt`做個修改,比如加上一行內容:
~~~
1612B 班 大家好嗎?
非常好!
那好,我們來學習git添加文件。
學習時光機的使用。
學習版本回退。
學習暫存區。
~~~
然后,在工作區新增一個`LICENSE`文本文件(內容隨便寫)。
先用`git status`查看一下狀態:
~~~
$ git status
On branch 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: readme.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
LICENSE
no changes added to commit (use "git add" and/or "git commit -a")
~~~
Git非常清楚地告訴我們,`readme.txt`被修改了,而`LICENSE`還從來沒有被添加過,所以它的狀態是`Untracked`。
現在,使用兩次命令`git add`,把`readme.txt`和`LICENSE`都添加后,用`git status`再查看一下:
~~~
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: LICENSE
modified: readme.txt
~~~
現在,暫存區的狀態就變成這樣了:

所以,`git add`命令實際上就是把要提交的所有修改放到暫存區(Stage),然后,執行`git commit`就可以一次性把暫存區的所有修改提交到分支。
~~~
$ git commit -m "understand how stage works"
[master d3b13e7] understand how stage works
2 files changed, 2 insertions(+)
create mode 100644 LICENSE
~~~
一旦提交后,如果你又沒有對工作區做任何修改,那么工作區就是“干凈”的:
~~~
$ git status
On branch master
nothing to commit, working tree clean
~~~
現在版本庫變成了這樣,暫存區就沒有任何內容了:

## 3.6 小結

暫存區是Git非常重要的概念,弄明白了暫存區,就弄明白了Git的很多操作到底干了什么。
沒弄明白暫存區是怎么回事的童鞋,請向上滾動頁面,再看一次。
跟上

- 班規
- 第一單元 Git
- 1.1 Git簡介
- 1.2 Git安裝
- 1.3 版本控制
- 1.4 遠程倉庫
- 1.5 分支管理
- 1.6 Git命令總結
- 1.7 在Android Studio中使用Git
- 第一單元 作業
- 第二單元 項目立項
- 2.1 需求文檔
- 2.2 原型圖
- 2.3 接口文檔
- 2.4 項目實現
- 2.5 制定開發計劃
- 第二單元 作業
- 第三單元 MVP搭建項目框架
- 3.1 代碼架構模式
- 3.2 普通方式寫代碼
- 3.3 使用MVC重構代碼
- 3.4 使用MVP重構代碼
- 3.5 使用接口提高代碼通用性
- 3.6 內存泄漏
- 3.7 使用契約統一管理接口
- 第三單元 作業
- 第四單元 MVP架構優化
- 4.1 MVP基類封裝與泛型應用
- 4.3 BaseActivity的封裝
- 第五單元 Volley網絡框架
- 5.1 Volley網絡框架
- 第六單元 Glide實現圖片異步加載
- 6.1 開始使用
- 6.2 占位符
- 6.3 Glide緩存
- 6.4 GlideGifVideo與色彩模式
- 第七單元 傳統屏幕適配
- 7.1 相對布局
- 7.1 權重
- 7.3 .9Patch
- 7.4 dimens適配
- 7.5 國際化
- 7.6 shape實現自定義樣式
- 7.7 自定義樣式
- 7.8 沉浸式狀態欄
- 第八單元 RecyclerView
- 8.1 RecycleView
- 第九單元 自定義View
- 第十單元 自定義View實戰
- 第十一單元 自定義View進階
- 第二十單元 屬性動畫
- 第十三單元 異常捕獲機制
- 第十四單元 原生登錄、注冊模塊
- 第十五單元 第三方登錄、分享、統計
- 第十六單元 HTML5新特性
- 第十七單元 CSS3新特性
- 第十八單元 WebView與JS交互
- 第一周周考
- 第二周周考
- 第三周周考
- 月考