解說:
https://ihower.tw/git/
# 基礎指令
1、安裝 git + source tree 實現可視化
查看文件:
~~~
ls
~~~
但有些時候文件是隱藏的,此時用到
~~~
git ls -la
~~~
即可查看隱藏文件
~~~
mkdir README
~~~
↑新建名為 README 的文件
~~~
rm -rf README
~~~
↑刪除名為 README的文件
~~~
git reset HEAD^ --hard
~~~
還原上一個動作
#### setup:可進行一些 git 非常基礎的預設。
~~~
git config --global user.name "amy326"
git config --global user.email "amy87326@163.com"
git config --global color.ui true
~~~
之后也可以輸入 以下命令,在隱藏文件.gitconfig中直接設置
~~~
vi ~/.gitconfig
~~~
比如設置快捷鍵,用 vi 命令打開.gitconfig 文件以后,按I進入編輯模式,增加以下行:
~~~
[alais]
ci = commit
ch = checkout
br = branch
st = status
~~~

# 顯示修改的內容
~~~
git diff
git diff --staged
~~~
↑git diff 可以顯示在工作區中被修改電但還沒放入暫存空間的內容
但如果已經 git add . 暫存了,則再輸入git diff則不顯示。此時要輸入"git diff --staged"
~~~
git add README(文件名)
~~~
↑則只對該文件進行暫存
~~~
git add .
~~~
↑將所有文件統統放入暫存中等待 commit
~~~
git log
~~~
↑顯示 commit 的時間和名字
~~~
.cat README
~~~
↑在屏幕上顯示文件里面具體內容但不打開文件
~~~
vi README(文件名)
~~~
↑可以直接進入該文件中,再用 vi 命令進行編輯
按 esc 退出后,輸入" :wq!" 即可存盤并退出。
~~~
git status
~~~
↑顯示文檔狀態。詳見《補充:Git 分區原理》
>可反悔:

## 文件操作
#### 刪除文件
~~~
git rm aaa
~~~
↑意思是在工作區中把名字叫aaa的文檔刪了并把這一情況記入stage暫存中
~~~
rm aaa
~~~
↑與上面的差別意思是只在工作區中刪除 aaa,但還未將這一情況放入 stage 中。
#### 重命名
~~~
git mv b BBB
~~~
↑把名為b的文件改名成 BBB,并放入暫存中。
#### 復制文件
~~~
cp BBB DDD
~~~
↑ 把 BBB 復制一份名叫 DDD。
## **反悔 revert**
如何把之前 commit 進去 git 的文件刪除:
比如上一步執行了復制命令并已經commit 了,但突然不想復制:
~~~
git revert HEAD
~~~
↑ 可以用 git revert 這個命令。后面的 HEAD 指的是最新的那次操作。如果是前一個,用HEAD^,前兩個就用HEAD^^
> 但一般我們可以打開 source tree,用流水號碼來執行。
比如上述,找到之前 commit 的文件,查看號碼為e44f87e9c01……
直接輸入
~~~
git revert e44f87e9c01 #不用全部打出來#
~~~

此時git會自動新增又一個commit 來記錄此次反悔(如圖的‘Revert 'add DDD' ’)。
#### 如何查看這個號碼
建議直接用source tree查看,但也可以在item 中輸入
~~~
git log --oneline
~~~
就可以看到了。按:wq退出查看。

## 貼標簽
一般可用于查找
~~~
git tag foo
~~~
↑給最新的那個 commit 貼一個叫 foo 的標簽
~~~
git tag AAA e44f87e9c01(號碼)
~~~
↑給號碼為e44f87e9c01的 commit 貼一個叫 AAAd 標簽
#### 刪除標簽
~~~
git tag -d AAA #或git tag AAA -d#
~~~
#### 貼備注
~~~
git tag CCC -m "hello" e44f87e9c01
~~~
↑給號碼為e44f87e9c01的 commit 貼一個名字叫 CCC 的標簽,并寫一個備注叫“hello”。

## 查看誰寫了什么代碼在哪個文件里
~~~
git blame README
~~~
↑可在 README 這個文件中查看誰寫了什么東西。按 “:q” 退出。
~~~
git blame README -L 100,10
~~~
↑從第100行開始往下查看10行。

## **砍掉untracked的檔案**
比如剛要刪除git rm a + git rm b比較麻煩,可以直接
~~~
git clean -n
~~~
列出要清理的檔案,再來
~~~
git clean -f
~~~
清理掉。
或者直接git clean -x 連在.gitignore里面的都統統清除。
其實效果等于
~~~
git add .
git stash
#先把新文檔全部暫存入 stage里面,再一次性 stash 清除#
~~~
↑但其實,git stash 不是刪除,而是先放在一個叫 stash 的地方。

## 設置.gitignore執行部分 commit
比如 touch rabish以后,rabish這個文檔不想被 commit 進去,可以執行
~~~
vi .gitignore
~~~
在里面輸入rabish 以后,這個文件就可以不被 commit 了。但要記得,.gitignore這個文件是要被 commit 才可以
~~~
git add .
git commit -m "Add .gitignore"
~~~
#### 空目錄(空文件夾)不會被 commit
如果新建mkdir SSS,但里面什么內容都沒有,那么 commit 的時候,這個文件是不會被記錄的。

但有時候我們又需要 commit 一些空目錄,此時用以下命令:
~~~
touch SSS/.gitkeep
~~~
再進行 commit 的時候,就會把SSS這個空文件夾放進去了。
## 分支
#### 開分支
~~~
git checkout -b feature1
~~~
#### 移動到分支
~~~
git checkout 分支名
~~~
如果輸入git branch 分支名,這是要新建的意思,要注意命令不用用錯。
#### 顯示有哪幾個分支 查看分支
~~~
git branch
~~~
如果是在 github 上,用這個命令看不到,于是可以用
~~~
git branch -r
~~~

顯示已被合并的分支
~~~
git branch -r --merged
~~~
#### 合并分支
一般是先到 master 主干以后,再把分支合進來
~~~
git br master
git merge feature1
~~~
如果commit分支中的內容后,又修改主干(**兩次修改的是不同文件**),則 commit 時候會都保留,只不過有一個小節點。

但如果 feature 與 master 都對同一個文件進行修改,就會有大沖突,此時需要根據提示來解決才可以。
#### 編輯的都是同一個文件的同一行
比如新建分支feature2并修改 README文檔使之第一行為hello
~~~
git ch -b feature2
vi README #假設此時文件時空的,我們在第一行輸入 hello#
git add .
git commit -m "Update README 1"
~~~
此時又不小心回到master主干并對同一個文件同一行進行編輯。(因為此時還沒把第二個分支feature2合并進來主干,所以如果此時回到主干并打開 README 文件時,仍是空的)
~~~
git ch master
vi README #此時看到的文件仍是空的,于是我們在第一行也輸入文字 hi222#
git add .
git ci -m "Update README 222"
~~~
此時在沒有合并以前,分歧已經產生。

若在master中執行合并,則會提示錯誤:both modified: README

打開這個檔案后會發現有提示:

此時只要保留真實想要的結果就可以。并且記得把大于號小于號等于號都去掉。再執行
~~~
git add README #文件名,只要針對這個有沖突的進行保存就可#
git commit #此時會跳出預設信息出來,確認一下,沒問題后只要:wq!存檔以后,就可以自動執行完成git merge#
~~~

#### 刪除分支
如果已經 merge 成功以后,再執行分支刪除,此時會顯示只剩下 master 這個分支。但原來在 feature1分支中做的內容其實是被保留沒有刪除的。
~~~
git branch fearure1 -d
#或者 git br -d feature 也一樣#
~~~
**強制刪除**
如果你的這個分支還沒被合并進來,那么執行大寫D,就可以強制刪除該分支,不用管是不是被合并。
~~~
git br feature1 -D
~~~


#### 重命名分支
~~~
git branch -m feature2 feature2b
~~~
↑直接把名字叫 feature2 的分支重命名為feature2
**強制重命名**
如果是你命名到一個已經存在的名字,那么執行大寫的M,則會強制覆蓋掉已經存在的那個同名文件。
~~~
git br -M feature2 feature2b
~~~
#### 人為制造流程圖小突起
~~~
git ch -b feature4
git ch master
git merge feature4 --no-ff
~~~
這樣可以很方便看到差別。也可以清楚看到,幾個 commit 是屬于同一個 branch 的。

#### 合并部分 commit 到 master
比如我們在開分支feature6的時候,寫了很多 commit,大部分暫時先不想合并到主干里面,但里面有一個 commit 比如是修改錯別字,想先合并到主干里面才不會影響開發。此時可以用 cherry-pick 功能
~~~
git ch master # 回到主干再操作#
git cherry-pick c224fce9840 #這條commit的號碼#
~~~


#### 開分支注意事項
如果有沖突,則無法 merge。但此時可以先 commit,只要不push都可以慢慢解決沖突。
可使用 git reset, 或者用git stash。
或者使用git stash 先暫存下來,放在一個叫wip的地方。
~~~
git stash
~~~
要再次顯示的話,可以
~~~
git stash apply
~~~
清除:
~~~
git stash clear
~~~

## 推送到遠端倉庫
一般使用https://github.com 來儲存 git。
#### ssh 碼設定
ssh 其實是一種加密方式,通訊協定,類似https:// 用來在你的計算機創建一個私鑰id_rsa以及公鑰id_rsa.pub。
~~~
ssh-keygen -t rsa -C"amy87326@163.com"
~~~
↑郵箱是 github 注冊的郵箱。直接按三個回車即可,也不需要輸入密碼,因為如果現在輸入,后續就會一直需要輸入,很討厭。
~~~
cat ~/.ssh/id_rsa.pub
~~~
↑顯示你的公鑰號,然后復制到github中新建一個 ssh 碼,并新建一個新的repository。
此時可以推送了
~~~
git remote add origin git@github.com:amy326/sandbox.git
git push -u origin master
~~~
>* 綁定 ssh 碼的時候,只輸入ssh-keygen 可能是不夠的,要輸入ssh-keygen -t rsa -C"郵箱號"
>* 新建完在調出 ssh時,如果輸入more ~/.ssh/id_rsa.pub 也不太合適,使用 cat 這個命令更好,否則可能在git push -u origin master的時候會出現“無法找到ssh”。
>* 在推送git push -u origin master時候,如果提示“無法找到ssh密碼”,則可以回到 cd 根目錄中,輸入cd ~/.ssd 移動到 .ssh這個隱藏文件中查看。
>
#### clone回來
~~~
git clone git@github.com:對方賬號/要拷貝的專案名字 本地你想新建的專案名字
~~~
比如:git clone git@github.com:lololo/sandbox amy_sandbox就是,我從 lololo 這個人的 github 中克隆一個名叫sandbox的專案,并且在本地中我自己取名叫amy_sandbox。
這個命令操作的意思是,第一次把別人的專案完整clone出來。但后續如果在執行小修改的時候,就不需要這個命令了,直接git pull 即可。
~~~
git pull
~~~
#### 沖突
如果遠端已經修改完了最新版,但是本地 git 倉庫沒有拉回來,又在同一個地方進行修改,那么如果你本地修改完以后想git push origin master,則會提示reject。
~~~
To github.com:amy326/sandbox.git
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'git@github.com:amy326/sandbox.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
~~~

此時需要先pull 下來,進行修改后再推上去。但會發現其實 pull 下來以后仍然會有錯,錯在 README 這個文件中。于是打開 README 這個文件,解決沖突。


#### 推送分支 push 分支
~~~
git push origin feature6
~~~
這時另一臺電腦如何拉 feature6?
~~~
git chekcout -t origin/feature6
~~~
出現以下錯誤提示:
~~~
fatal: Cannot update paths and switch to branch 'feature6' at the same time.
Did you intend to checkout 'feature6b' which can not be resolved as commit?
~~~

此時可以更新遠端倉庫git fetch,再繼續git checkout -t origin/feature6。如果再有沖突,可以把本地的feature6先刪除,再直接克隆遠端的。
~~~
git fetch
git checkout -t origin/feature6
~~~
## 合并分支的方式
reset
rebase
reverse
merge
## git rebase重新整理 commit
類似 merge,但有點不一樣。
**整理線型**
一般在分支要合并進主干前,可以用 git rebase 這個 功能來再整理一下 commit,讓別人更好理解每個 commit 的意思。
~~~
git rebase xxx節點 -i #進入互動模式#
~~~
常用的有:
#### 在某個節點之后插入新的一個 commit
插入 commit
可以用到edit 這個功能,只要把默認的pick改成edit 就可以。一般加入 git reset HEAD^,回到你要修改的 commit 之前的一個節點,先重置git reset hEAD^ ,然后 再繼續編輯。
~~~
git reset HEAD^
~~~
比如你可以 edit 把原來的一個 commit 變成2個(這個操作就是你只要跟平時一樣 commit 兩次就可以),然后再繼續git rebase --continue繼續進行,就可以。
~~~
git add . #有沖突要先修好,并 add 進去stage才可以#
git rebase --continue #可繼續把rebase進行完#
~~~

最好在某個分支里面。因為 git rebase xxx -i 出現互動框后,只會出現你所在的分支的相關 commit 節點,不會出現其他分支的節點,所以如果有分叉出去的 commit,則會被跳過。

#### 刪除某個 commit
進入 -i 互動模式,只要找到對應的那行,直接刪除(編輯狀態下按兩下 dd),再繼續git rebase --continue 即可。
#### 保留某個 commit 內容,但不單獨占用一個 commit 而是合并到前一個 commit 進去
用字母 f
#### 改 commit 信息
用字母 r
#### 調換順序
也是一樣,進入 -i 互動模式,把那兩行直接剪切黏貼到合適的位置就可以。但要記得這是最危險的,因為會非常容易造成沖突。
#### 改善線型
git rebase 以后,commit 的線型可能不是非常好看,比如這個feature/forum 分支,如下圖有點亂。

我們可以通過 git rebase master 命令讓它重新回到 master 里面,變好看點:
~~~
git checkout feature/forum #切換到想修改線型的分支#
git rebase master #從分支往主干合并,當然也可以合并到其他分支 git rebase feature1等#
git add . #如果有沖突,應先解決,并輸入git add .#
git rebase --contimue #繼續把沒完成的 rebase 進行完#
~~~

## git reset砍掉
與 revert 不同,reset 是直接砍掉,不留痕跡。
#### 回到要修改的節點
~~~
git reset xxx
git commit -a --amend
~~~

~~~
git reset xxx號碼 #重置 xxx 這個 commit#
git reset HEAD^ #保存完馬上后悔,于是執行這個文件以后,需要 commit 的內容就會還原到在 working spsce 里面待操作#
git reset HEAD^ --soft #修改后放到 stage 里面#
git reset HEAD^ --hard #直接清除#
~~~
## 項目管理分支模式
* develop on mainline
這樣的好處是比較清楚,避免后續 merge 的問題;
缺點是速度非常慢,需要前續完成后才能進行,后續必須有增量開發的能力(因為只有一條主線)。

* branch for release
比如主線是3.0版本,要更新3.1版本的時候,就開一個 release3.1的 branch ,后續也同理。
主要用來修復master 版本的bug。
* branch by feature
根據功能來切分,比較常見。優點多,但要避免開太多。另外需要有一個 leader 來負責主要 merge。
但需要大家都時不時更新主干 code。
* branch by team
也常見。

#### 兩個主要分支
master——主干,永遠處于最新狀態
develop——下一次要發布的版本,目前開發用。

#### 三種支援分支

* Feature
開發新功能,或者修 bugs;
從 develop 分出來,完成后 merge 到 develop 中;
如果開發時間長,建議定期同步develop 主干的 code,不然后期可能 merge 不進去
新手用 merge,進階的建議用 rebase。
可以另設--no-ff的merge節點。

* Release branches
準備要 release 的版本,只修復bug;
從 develop 分出來,完成后 **merge** 回 master和 develop。
* Hotfix branches
等不及 release,必須馬上修復的 bug;
一般從 master 分出來,完成后 merge 回 master 和 develop。

## 更多 git 功能
* git archive
* git bisect
* git blame
* git grep
* git show
* git gc
* git format-path, send-email, am
* git pull --rebase
可以避免不必要的 merge 節點。
## 弄丟了commit 怎么辦
找回:
例如搞砸了 git reset, rebase 或誤刪branch
在git gc前都有機會,因為預設會保留孤立的 commit90天。
* git reflog 或 git fsck --lost-found
* git cherry-pick xxx號碼
* git merge xxx號碼
## git 儲存
~~~
git cat-file -p xxxsha1號碼
~~~
↑可顯示文件位置