├── Git-Flow └── README.md ├── LICENSE ├── Multiple_SSH_Keys_settings.md ├── README.md ├── git_submodule_turorial.md ├── git_subtree_turorial.md └── pr-tutorial └── README.md /Git-Flow/README.md: -------------------------------------------------------------------------------- 1 | # Git-Flow 基本教學 :memo: 2 | 3 | [Git-Flow Tutorials - youtube](https://youtu.be/zXlta66thZY) 4 | 5 | [Git-Flow SmartGit Tutorials - youtube](https://youtu.be/ualXHytifbg) 6 | 7 | 在開始要了解 Git-Flow 之前,建議大家先對 Git 有基本的認識, 8 | 9 | 可以參考我之前寫的 [Git-Tutorials GIT基本使用教學](https://github.com/twtrubiks/Git-Tutorials) 10 | 11 | ## 什麼是 Git-Flow 12 | 13 | 在 Git 中有很多指令, 如果你使用 Git-Flow ,會多一些額外的功 14 | 15 | 能,但這先功能都只是由基本功能下去組合出來的,講簡單一點 16 | 17 | ,就是 A = B + C + D 的概念 ,當然,也不用擔心需要額外記指令 18 | 19 | ,有 GUI 介面 ( 有很多套可以使用,我是使用 [SmartGit](http://www.syntevo.com/smartgit/) ) ,而且很方便。 20 | 21 | ## 為什麼要使用 Git-Flow 22 | 23 | 當你一個人在用 Git 的時候一定很爽,你想怎麼 commit 或 24 | 25 | 者怎麼 merge 甚至在哪個分支下開發你都可以自己決定,但 26 | 27 | 如果是多人共同開發呢 ? 每個人都有自己的習慣,這樣豈不 28 | 29 | 是天下大亂,於是 Git-Flow 就產生了。 30 | 31 | ## Git-Flow 並不是一個新技術,他只是一個規範 32 | 33 | 很多人第一次看到 Git-Flow 都會想,哇!這個什麼東西阿,好潮 34 | 35 | 哦之類的,但他只是 **一種規範** ,並不是一種新技術,當一整個 36 | 37 | 團隊都遵守某一種工作流程,比較不會發生問題。 38 | 39 | Git-Flow 是訂出來的一種規範,如果你或公司對 Git 非常了解, 40 | 41 | 你們也可以自己定義屬於你們自己的 Flow ,不要遵守 Git-Flow , 42 | 43 | 是完全不會有衝突的。 44 | 45 | ## Git-Flow Model 46 | 47 | 在 Git-Flow 中,有兩個主要的分支: 48 | 49 | ***Master*** 50 | 51 | 這個分支是非常穩定的,任何時刻都是 production-ready ,包含最新的 release ,也就是產品的原始碼,當任何一個人使用你的產品,第一眼看到的都是 master,**我們不能直接在 master 分支上進行任何的 commit ,只能從 Release 或 Hotfix merge 回來**。 52 | 53 | ***Develop*** 54 | 55 | 開發中的分支,develop 分支是由 mater 分支分出來的,該 ( develop ) 分支提供整合不同即將 release 的 feature ( 功能 ) ,所以該分支有可能不穩定 ( 有bug ) 。通常我們也不會直接在該分支下 commit,而是透過 merge 的方式將 feature ( 功能 ) merge 進來。 56 | 57 | Master 以及 Develop 這兩個分支非常重要,理論上要保護好這兩個分支,避免被意外刪除。 58 | 59 | 除了 **Master** 以及 **Develop** 這兩個分支之外,Git-Flow 還提供其他的分支: 60 | 61 | ***Feature*** 62 | 63 | 功能開發,該分支是由 develop 分支分出來的,最後會被合併回 develop 分支。舉個例子,假設目前有 A 和 B 兩個功能經由兩個人下去開發,這兩個人會分別從 develop 分支分 A 和 B 兩個分支出來,當完成後,再 merge 回 develop 。 64 | 65 | ***Release*** 66 | 67 | 當我們認為 develop 已經是一個很穩定的版本時,我們會進入 release,該分支是由 develop 分支分出來的。通常我們會在該分支底下再做一次全面的測試以及上線前的準備 ( 發佈版本的記錄 ),完成 release 後,我們會 merge 回 master 以及 develop。 68 | 69 | ***Hotfix*** 70 | 71 | 該分支是由 master 分支分出來的,通常是已經上線了,但突然發現一個非常緊急的 bug ,這時候我們就會開一個 Hotfix 出來修復該 bug ,完成後我們會再 merge 回 master 以及 develop。 72 | 73 | ## 使用 SmartGit 來完成 Git-Flow 74 | 75 | 類似的 GUI 介面有很多,大家可以找適合自己的使用,我在這邊用 [SmartGit](http://www.syntevo.com/smartgit/) 介紹。 76 | 77 | ### 安裝 SmartGit 78 | 79 | 請到 [SmartGit](http://www.syntevo.com/smartgit/) 下載符合自己的作業系統, 80 | 81 | 安裝基本上不會有甚麼問題,比較需要注意的地方是如果你是非商用版, 82 | 83 | 請選擇非商用 ( 如下圖 ) ,假如是商用版,請購買 License 84 | 85 | ![](http://i.imgur.com/Qo6iy0l.jpg) 86 | 87 | 設定一些東西即可完成 88 | 89 | ![](http://i.imgur.com/thRWcvv.jpg) 90 | 91 | 進到 SmartGit 後,請先選好專案,接著我們按 Git-Flow 的圖示 ( 如下圖 ) 92 | 93 | ![](http://i.imgur.com/MbJPEIF.jpg) 94 | 95 | 通常我們 Git-Flow Type 會選 **Full** , 也就是完整的分支 96 | 97 | ![](http://i.imgur.com/xnUn9vS.jpg) 98 | 99 | 你會發現左下角的兩個主要分支跑出來了 100 | 101 | ![](http://i.imgur.com/XfWWByZ.jpg) 102 | 103 | 如要新增任何的 Feature 或 Release ,可以點選 Git-Flow 的圖示 104 | 105 | ![](http://i.imgur.com/0147G33.jpg) 106 | 107 | 更多詳細的介紹可參考 108 | 109 | [Git-Flow SmartGit Tutorials - youtube](https://youtu.be/ualXHytifbg) 110 | 111 | ## Reference 112 | 113 | * [gitflow](https://github.com/nvie/gitflow) 114 | 115 | ## Donation 116 | 117 | 文章都是我自己研究內化後原創,如果有幫助到您,也想鼓勵我的話,歡迎請我喝一杯咖啡 :laughing: 118 | 119 | ![alt tag](https://i.imgur.com/LRct9xa.png) 120 | 121 | [贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8) 122 | 123 | ## License 124 | 125 | MIT license 126 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Multiple_SSH_Keys_settings.md: -------------------------------------------------------------------------------- 1 | # Multiple SSH Keys settings for different github account 2 | 3 | 如果你對這個真的很陌生,建議看影片 :smile: 4 | 5 | * [Youtube Tutorial - Multiple SSH Keys settings for different github account](https://youtu.be/gDxG-4tF7B8) 6 | 7 | ## 前言 8 | 9 | 有時候我們可能會遇到這種情境,就是同一台電腦上同時需要有多個 github 帳號,為什麼呢? 10 | 11 | 當你工作的電腦和你自己的 side project 開發都在同一台電腦的時候,你就需要這篇文章的幫忙 12 | 13 | 了 :kissing_smiling_eyes: 14 | 15 | 相信我,你不會希望用公司的帳號推 commit 到自己的 side project 上 :sweat_smile: 16 | 17 | ## 教學 18 | 19 | 第一步,先產生 ssh key,如果你不知道怎麼產生, 20 | 21 | 可參考 [Generating a new SSH key](https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/) 或是 [github基本教學 - 從無到有](https://www.youtube.com/watch?v=py3n6gF5Y00), 22 | 23 | 打開你的 Git Bash, 24 | 25 | ```cmd 26 | ssh-keygen -t rsa -b 4096 -C "blue555236sa56@gmail.com" 27 | ``` 28 | 29 | ( 請將信箱換成自己的 ) 30 | 31 | 這裡的重點是記得自行輸入新的 rsa 名稱,像是下圖的 `/c/Users/twtrubiks/.ssh/id_rsa_rubik` 這樣, 32 | 33 | 這樣才不會覆蓋舊有的 rsa, 34 | 35 | ![alt tag](https://i.imgur.com/2Msr51U.png) 36 | 37 | 接下來,你的 `.ssh` 路徑底下會多出你剛剛建立的檔案 38 | 39 | ![alt tag](https://i.imgur.com/B4g9FQO.png) 40 | 41 | 再把你的 `id_rsa_rubik.pub` key 貼到你的 gihub 上, 42 | 43 | ![alt tag](https://i.imgur.com/tfQzFcJ.png) 44 | 45 | 通常你新增成功的時候,信箱會收到一封信。 46 | 47 | 再輸入下方指令 48 | 49 | ```cmd 50 | ssh-agent -s 51 | ``` 52 | 53 | 上面這個指令一定要執行,如果跳過這個步驟,就會出現以下錯誤, 54 | 55 | Could not open a connection to your authentication agent. 56 | 57 | 所以請記得一定要執行。 58 | 59 | 在 `.ssh` 目錄底下建立一個名稱為 `config` 的檔案 ( 檔名就是 `config`,副檔名不用填 ), 60 | 61 | ![alt tag](https://i.imgur.com/SJBxro6.png) 62 | 63 | 下方是一個範例, 64 | 65 | ```config 66 | # github - twtrubiks 67 | Host github.com 68 | HostName github.com 69 | IdentityFile ~/.ssh/id_rsa 70 | IdentitiesOnly yes 71 | User twtrubiks 72 | 73 | # github - blue-rubiks 74 | Host blue.github.com 75 | HostName github.com 76 | IdentityFile ~/.ssh/id_rsa_rubik 77 | IdentitiesOnly yes 78 | User blue-rubiks 79 | ``` 80 | 81 | 可用以下指令測試,如果顯示出自己的使用者名稱,代表設定成功 82 | 83 | ```cmd 84 | ssh -T git@github.com 85 | ``` 86 | 87 | ![alt tag](https://i.imgur.com/rdLf4iX.png) 88 | 89 | ```cmd 90 | ssh -T git@blue.github.com 91 | ``` 92 | 93 | ![alt tag](https://i.imgur.com/YTNHPfN.png) 94 | 95 | ya :heart_eyes: 我們設定成功惹~ 96 | 97 | 如果有問題,也可以用 debug 模式看問題出在哪裡,通常是 `config` 輸入有誤 98 | 99 | ```cmd 100 | ssh -vT git@github.com 101 | ``` 102 | 103 | 接下來先和大家說明一下, 104 | 105 | ```cmd 106 | git config --global user.name "blue-rubiks" 107 | git config --global user.email "blue555236sa56@gmail.com" 108 | ``` 109 | 110 | `--global` 顧名思義就是全域的意思,如果沒輸入,就代表是目錄底下的 repo 會生效, 111 | 112 | 現在我們 clone 一個 repo 來玩玩看 (你可以自己建立一個) 113 | 114 | ```cmd 115 | git clone git@github.com:blue-rubiks/github-ssh-test.git 116 | ``` 117 | 118 | 接著,切換到目錄資料夾底下,輸入以下指令 119 | 120 | ```cmd 121 | git config user.name "blue-rubiks" 122 | git config user.email "blue555236sa56@gmail.com" 123 | ``` 124 | 125 | 注意,這邊沒加上 `--global`,所以只有該目錄底下的 repo 會生效, 126 | 127 | 我們找到 repo 中的 `.git` 資料夾 , 再找到 `config` 檔案,內容大致如下 128 | 129 | ![alt tag](https://i.imgur.com/vT6GiYR.png) 130 | 131 | 主要就是修改第 9 行成第 10 行,修改為你設定的 `Host`, 132 | 133 | 最後,可以安心 commit 以及 push 了。 134 | 135 | 如果你真的聽不懂我在說什麼,建議看影片說明,我會帶大家操作一波 :laughing: 136 | 137 | ## 執行環境 138 | 139 | * Win 10 140 | 141 | ## Reference 142 | 143 | * [Multiple SSH Keys settings for different github account](https://gist.github.com/jexchan/2351996) 144 | 145 | ## Donation 146 | 147 | 文章都是我自己研究內化後原創,如果有幫助到您,也想鼓勵我的話,歡迎請我喝一杯咖啡 :laughing: 148 | 149 | ![alt tag](https://i.imgur.com/LRct9xa.png) 150 | 151 | [贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8) 152 | 153 | ## License 154 | 155 | MIT licens -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git-Tutorials 基本使用教學 :memo: 2 | 3 | 因為小弟覺得這東西蠻有趣的,所以就簡單寫個教學文,順便記錄一下 :memo:,希望能幫助想學的人 :smile: 4 | 5 | 如果教學有誤再請糾正 :sweat_smile: 6 | 7 | 基本使用指令以及安裝可參考小弟之前拍的影片 8 | 9 | * [Youtube Tutorial - github基本教學 - 從無到有](https://www.youtube.com/watch?v=py3n6gF5Y00) 10 | 11 | 影片教學包含如何產生 **SSH key** 12 | 13 | 如果步驟正確且沒出錯誤,可以在路徑下找到 **.ssh資料夾**,裡面有 **id_rsa** 以及 **id_rsa.pub** 兩個檔案, 14 | 15 | 這兩個就是 SSH Key, **id_rsa是私鑰** ,不能洩露出去, **id_rsa.pub是公鑰** ,可以很放心的告訴任何人。 16 | 17 | 安裝完 Git 之後,要做的第一件事情就是去設定自己的名字和信箱 18 | 19 | ```cmd 20 | git config --global user.name "twtrubiks" 21 | git config --global user.email "twtrubiks@gmail.com" 22 | ``` 23 | 24 | 可以輸入以下來確認是否輸入成功 25 | 26 | ```cmd 27 | git config --global user.name 28 | git config --global user.email 29 | ``` 30 | 31 | ![alt tag](https://i.imgur.com/5mpS7Ij.jpg) 32 | 33 | Git 設定資料查看,可執行以下指令 ( 文章末會有較詳細的教學 ): 34 | 35 | ```cmd 36 | git config --list 37 | ``` 38 | 39 | ## git init 指令 40 | 41 | 初始化 git 42 | 43 | ```cmd 44 | git init 45 | ``` 46 | 47 | 也可以指定資料夾 48 | 49 | ```cmd 50 | git init 51 | ``` 52 | 53 | ## git clone 指令 54 | 55 | 複製如圖位置網址 ( 不要複製我的哦~ 複製你自己的 ) 56 | ![alt tag](https://i.imgur.com/EJ5JNjt.jpg) 57 | 58 | git clone ( 複製的網址 ) SSH / HTTPS 59 | 60 | ( 如果你要使用 https 的方式, 請接著看 [Personal Access Tokens](https://github.com/twtrubiks/Git-Tutorials#personal-access-tokens) ) 61 | 62 | ```cmd 63 | git clone git@github.com:twtrubiks/test.git 64 | ``` 65 | 66 | 第一次會出現 SSH 警告,選 YES 即可。 67 | 68 | 如圖 ( 下載成功 ),在你的下載路徑下就會多出一個資料夾 69 | 70 | ![alt tag](https://i.imgur.com/iIkTlqf.jpg) 71 | 72 | ## Personal Access Tokens 73 | 74 | * [Youtube Tutorial - GitHub 教學 - Personal Access Tokens](https://youtu.be/aJRRVCB85k8) 75 | 76 | 從 2021/8/13 開始, 如果你用 https 的方式你會發現 77 | 78 | ![alt tag](https://i.imgur.com/6YIJSaj.png) 79 | 80 | ```text 81 | remote: Support for password authentication was removed on August 13, 2021. 82 | remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication. 83 | fatal: Authentication failed for 'https://github.com/xxxxx.git/' 84 | ``` 85 | 86 | 這時候如果我們不想加入 ssh key, 也不想透過加入共同協做的方式, 87 | 88 | 可以透過這個 Personal Access Tokens (你可以把他想成臨時的權限), 89 | 90 | 先到你的 github 裡的 Settings -> Developer settings, 91 | 92 | 選 Personal Access Tokens, 產生你的 token 93 | 94 | ![alt tag](https://i.imgur.com/zPVlOjf.png) 95 | 96 | 時間這邊你可以自己定義多久後會過期, 97 | 98 | 下面的部份則是這個 token 有哪些權限, 99 | 100 | ![alt tag](https://i.imgur.com/NNJcYRM.png) 101 | 102 | 設定完之後, 就可以複製你的 token 103 | 104 | ![alt tag](https://i.imgur.com/q4htIBn.png) 105 | 106 | 再回去用 https clone 的方式, 107 | 108 | 原本是使用, 帳號 + password (已經不能使用了), 109 | 110 | 現在改成, 帳號 + 剛剛的 token 就可以順利 clone 了. 111 | 112 | ### 如何改善(加速)大型 repo git clone 速度 113 | 114 | * [Youtube Tutorial - 如何改善(加速)大型 repo git clone 速度](https://youtu.be/YHX0qkQa1UI) 115 | 116 | 有時候我們會需要 clone 很大的 repo,執行 `git clone` 都需要很長的時間,是不是有方法可以 117 | 118 | 加速 clone 的速度呢 :question: 119 | 120 | 直接開始動手嘗試 ( 使用 [django](https://github.com/django/django) 當範例 ), 121 | 122 | `git clone git@github.com:django/django.git` 123 | 124 | ( 你會發現 clone 需要一些時間 :triumph:) 125 | 126 | ![alt tag](https://i.imgur.com/yMH6L8F.png) 127 | 128 | 接著查看 log,`git log` 129 | 130 | ![alt tag](https://i.imgur.com/vJkFTr2.png) 131 | 132 | 嘗試切換 branch `git checkout stable/2.2.x` 133 | 134 | ![alt tag](https://i.imgur.com/UtxJ2ER.png) 135 | 136 | 開始改善(加速) clone 的時間, 137 | 138 | 可以透過 `--depth` 這個參數來完成,簡單說明一下他的功能,當我們一般執行 clone 之後, 139 | 140 | 接著執行 `git log` 你會發現有大量的 log,在某修情況下,你可能不需要那麼多的 log, 141 | 142 | 也就是說你可能只需要最近 10 筆的 history commit,甚至你只需要 1 筆 ( 也就是根本不需要 143 | 144 | history commit ),這時候就很適合使用 `--depth`。 145 | 146 | `git clone git@github.com:django/django.git --depth 1` 147 | 148 | ( 你會發現這次快很多了 ) 149 | 150 | ![alt tag](https://i.imgur.com/yvkZUZI.png) 151 | 152 | 接著查看 log,`git log` 153 | 154 | ( 會變快的原因是因為我們只保留最新的一筆 history commit , 155 | 156 | 如果你需要最近 10 筆,改成 --depth 10 即可 ) 157 | 158 | ![alt tag](https://i.imgur.com/at9Zzq3.png) 159 | 160 | 但是會有一個問題,當嘗試切換 branch `git checkout stable/2.2.x` 161 | 162 | ( 你會發現你無法切換 remote branch :scream: 163 | 164 | 原因是因為使用 `--depth` 相當於是 `--single-branch`, 165 | 166 | 所以當然沒有其他的 branch。 ) 167 | 168 | ![alt tag](https://i.imgur.com/gDaeq1W.png) 169 | 170 | 也就是說以下兩條指令其實是相等的 171 | 172 | ```cmd 173 | git clone git@github.com:django/django.git --depth 1 174 | git clone git@github.com:django/django.git --depth 1 --single-branch 175 | ``` 176 | 177 | 為了解決這個問題,比較好的做好應該是這樣 178 | 179 | ```cmd 180 | git clone git@github.com:django/django.git --depth 1 --no-single-branch 181 | ``` 182 | 183 | ( 這個和 `--single-branch` 比會稍微久一點點,因為每個 branch 的最新一個 history commit 都要 clone 下來 ) 184 | 185 | 這樣的話,就可以保留 remote 的 branch 了, 186 | 187 | ![alt tag](https://i.imgur.com/BkLKVZz.png) 188 | 189 | 成功切換 remote 的 branch, `git checkout stable/2.2.x`。 190 | 191 | ![alt tag](https://i.imgur.com/VCvcSTr.png) 192 | 193 | 最後稍微整理, 194 | 195 | 如要 clone 最近一次的 history,而且也需要其他 branch,使用如下, 196 | 197 | `git clone git@github.com:django/django.git --depth 1 --no-single-branch` 198 | 199 | 如果你想要指定分支, 加上 `-b`, 200 | 201 | `git clone git@github.com:django/django.git --depth 1 --no-single-branch -b stable/3.1.x` 202 | 203 | 如要 clone 最近一次的 history,而且**不需要**其他 branch,使用如下, 204 | 205 | `git clone git@github.com:django/django.git --depth 1 --single-branch` 206 | 207 | or 208 | 209 | `git clone git@github.com:django/django.git --depth 1` 210 | 211 | 更多詳細參數說明請參考 [git clone](https://git-scm.com/docs/git-clone) 212 | 213 | ## git status 指令 214 | 215 | ```cmd 216 | git status 217 | ``` 218 | 219 | 可以讓我們觀看目前的 repository ( repo 容器 )。 220 | 221 | ![alt tag](https://i.imgur.com/5Gt98Vh.jpg) 222 | 223 | 意思是目前你的工作區是乾淨的。 224 | 225 | ## 工作區與暫存區 ( Stage ) 226 | 227 | git add 意思是把要送出的文件放到暫存區 ( Stage ) , 228 | 229 | 然後執行 230 | 231 | git commit 就可以把暫存區 ( Stage ) 裡所有修改的內容送到目前的分支上。 232 | 233 | 一旦送出 ( git commit ) 後,如果你又沒有對工作區做任何修改,那麼工作區就是"乾淨"的。 234 | 235 | git commit -m "xxxxx" 指令,-m 後面輸入的內容是本次修改 ( 送出 ) 的說明, 236 | 237 | 盡量輸入一眼就可以看出這次送出修改了什麼的內容 238 | ( 方便以後回去觀看能快速了解此次 commit 修改了什麼 )。 239 | 240 | 以下 demo 為在一個資料夾內新增一個 Hello.py 檔案 241 | 242 | 然後使用 git status 觀看目前的 repository ( repo 容器 ),你會看到 Hello.py 未被追蹤,如下圖 243 | 244 | ![alt tag](https://i.imgur.com/dvj1DQh.jpg) 245 | 246 | 可以使用如下指令 247 | 248 | ```cmd 249 | git add Hello.py 250 | ``` 251 | 252 | 額外補充,下面這個指令很有趣,大家可以玩玩看 253 | 254 | ```cmd 255 | git add -p 256 | ``` 257 | 258 | 接著再使用 259 | 260 | git commit -m "文字" 261 | 262 | ```cmd 263 | git commit -m "add Hello.py" 264 | ``` 265 | 266 | 再使用 git status,你會發現工作區變乾淨了。如下圖 267 | 268 | ![alt tag](https://i.imgur.com/6VrieNb.jpg) 269 | 270 | 補充,如果只有輸入 271 | 272 | ```cmd 273 | git commit 274 | ``` 275 | 276 | ![alt tag](https://i.imgur.com/yZxKGTU.jpg) 277 | 278 | 這時會跳出編輯視窗 279 | 280 | ![alt tag](https://i.imgur.com/htNQ0dJ.jpg) 281 | 282 | 這時可以按鍵盤的 **Ins鍵** ( 或按鍵盤上的 **英文字 i** ) 即可輸入文字 283 | 284 | ![alt tag](https://i.imgur.com/NFy16dp.jpg) 285 | 286 | 輸入完先按 **Esc鍵** ,按完後底下的 INSERT 會消失,接著直接打 **:wq** ,再按 enter 就會儲存並離開了。 287 | 288 | 更多參數可參考 [https://git-scm.com/docs/git-commit](https://git-scm.com/docs/git-commit) 說明。 289 | 290 | **如何修改最後一次的commit呢 ?** 291 | 292 | 有時候我們 commit 完之後,才發現自己的 commit 內容手殘打錯了 293 | 294 | 這時候可以使用如下指令,他會跳出編輯視窗給你編輯你上一次的 commit 內容。 295 | 296 | ```cmd 297 | git commit --amend 298 | ``` 299 | 300 | 又或是我們 commit 完之後,才發現自己漏了幾個檔案沒有 add 進去 301 | 302 | 這時候可以使用如下指令 303 | 304 | ```cmd 305 | git commit -m "init commit" 306 | git add missing_file.py 307 | git commit --amend 308 | ``` 309 | 310 | 如上狀況為當我 git commit -m "init commit" 之後, 311 | 312 | 我發現我漏掉了 **missing_file.py** 這個檔案 ( commit 前忘記 add 進去 ) , 313 | 314 | 這時候就可以使用 git commit --amend 來修改最後一次的 commit 。 315 | 316 | 有時候我們會為了方便,直接使用下面的指令一次加入全部的檔案 317 | 318 | ```cmd 319 | git add . 320 | ``` 321 | 322 | 但是加完後發現其實有些檔案是不需要 add 進入的,這時候就可以使用如下指令去取消 add 323 | 324 | ```cmd 325 | git reset HEAD 326 | ``` 327 | 328 | 範例,路徑下有 A.py 以及 B.py 這兩個檔案,然後我使用 **git add .** 加入, 329 | ![alt tag](https://i.imgur.com/0S7TcEB.jpg) 330 | 331 | 但加入完我發現其實 B.py 我還沒有要 add 進入,所以我這時候就可以使用 **git reset HEAD B.py** 去還原。 332 | 333 | ![alt tag](https://i.imgur.com/3iAyEEx.jpg) 334 | 335 | ## git push 指令 336 | 337 | ```cmd 338 | git push 339 | ``` 340 | 341 | 將程式 push 到 github ( or bitbucket 之類 )上 , 如下圖 342 | 343 | ![alt tag](https://i.imgur.com/d61Pau6.jpg) 344 | 345 | ## 版本控制 - 歷史記錄 346 | 347 | ```cmd 348 | git log 349 | ``` 350 | 351 | 按 **小寫q** 可退出 352 | 353 | ![alt tag](https://i.imgur.com/j11afCP.jpg) 354 | 355 | 如果覺得版面太雜,可以使用下列指令 356 | 357 | ```cmd 358 | git log --pretty=oneline 359 | ``` 360 | 361 | 按 **小寫q** 可退出 362 | 363 | ![alt tag](https://i.imgur.com/jz2cwUA.jpg) 364 | 365 | 如果你想要看某個檔案或某個資料夾的變動, 可以這樣使用, 範例如下 366 | 367 | * [Youtube Tutorial - Git Log:資料夾移動後 Log 消失?用 --follow 找回檔案歷史!(等待新增)](xx) 368 | 369 | ```cmd 370 | git log -- folder 371 | ``` 372 | 373 | 如果追蹤個別檔案, 可以加上 `--follow` 374 | 375 | ```cmd 376 | git log --follow -- folder/demo.py 377 | ``` 378 | 379 | 舉個例子, 我有一個資料夾結構如下 380 | 381 | ```cmd 382 | ❯ tree 383 | . 384 | ├── production 385 | │   ├── a 386 | │   │   ├── a1.txt 387 | │   │   ├── a2.txt 388 | │   │   └── a3.txt 389 | │   └── b 390 | │   └── b1.txt 391 | └── test 392 | ``` 393 | 394 | `production` 下的歷史紀錄 395 | 396 | ```cmd 397 | ❯ git log -- production/a 398 | 399 | * b9a481b - a3.txt 400 | * 02fce19 - a2.txt 401 | * 1e4aa1b - create 402 | ``` 403 | 404 | 假設今天把 `production` 底下的 `a` 資料夾移動到 `test` 資料夾底下, 405 | 406 | 資料夾結構變成如下, 然後 commit 407 | 408 | ```cmd 409 | ❯ tree 410 | . 411 | ├── production 412 | │   └── b 413 | │   └── b1.txt 414 | └── test 415 | └── a 416 | ├── a1.txt 417 | ├── a2.txt 418 | └── a3.txt 419 | ``` 420 | 421 | 目前整個 repo 的 git log 如下 422 | 423 | ```cmd 424 | ❯ git log 425 | 426 | * 5707ce2 - move 427 | * b9a481b - a3.txt 428 | * 02fce19 - a2.txt 429 | * 1e4aa1b - create 430 | * 1b7fb87 - Initial commit 431 | ``` 432 | 433 | 然後再看 `a` 資料夾, 如下 434 | 435 | ```cmd 436 | ❯ git log -- test/a 437 | 438 | * 5707ce2 - move 439 | ``` 440 | 441 | 你會發現前面的 `a2.txt` 以及 `a3.txt` 的 git 的追蹤紀錄消失了 !!! 442 | 443 | 方法一, 追蹤原資料夾, 444 | 445 | 雖然這個檔案已經不存在了, 但是對 git 來說紀錄還是在的 446 | 447 | ```cmd 448 | # 這邊你會發現資料夾已經不存在的, 因為已經移動了 449 | ❯ ls production/a 450 | 451 | ls: cannot access 'production/a': No such file or directory 452 | 453 | # 但是還是可以追蹤到歷史紀錄 454 | ❯ git log -- production/a 455 | 456 | * 5707ce2 - move 457 | * b9a481b - a3.txt 458 | * 02fce19 - a2.txt 459 | * 1e4aa1b - create 460 | ``` 461 | 462 | 方法二, 加上 `--follow`, 這個可以追蹤個別檔案 (移動後的資料夾不要用它, 因為會看不到過去紀錄) 463 | 464 | ```cmd 465 | ❯ git log --follow -- test/a/a1.txt 466 | 467 | * 5707ce2 - move 468 | * 1e4aa1b - create 469 | ``` 470 | 471 | 另外底下也是一個看 log 的方式( 很酷 :satisfied:),有 GUI 的感覺( 來源為文章最後的連結 ) 472 | 473 | ```cmd 474 | git log --graph --pretty=format:"%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset" --abbrev-commit --date=relative 475 | ``` 476 | 477 | ![alt tag](https://i.imgur.com/XNQisuf.png) 478 | 479 | Git 中,使用 HEAD 表示目前的版本, 480 | 481 | ```cmd 482 | git reset --hard HEAD 483 | ``` 484 | 485 | ![alt tag](https://i.imgur.com/pkFO8pk.jpg) 486 | 487 | 如果現在要把目前版本退回到上一個版本,就可以使用 git reset 指令: 488 | 489 | 上一個版本就是HEAD~1, 490 | 491 | ```cmd 492 | git reset --hard HEAD~1 493 | ``` 494 | 495 | ![alt tag](https://i.imgur.com/ZThoaUT.jpg) 496 | 497 | 上上一個版本就是HEAD~2, 498 | 499 | 如果要指定回到某個特定版本: 500 | 501 | ![alt tag](https://i.imgur.com/KrCOC71.jpg) 502 | 503 | ```cmd 504 | git reset --hard ad41df36b7 505 | ``` 506 | 507 | `--hard` 這個參數,有三種選擇,分別為 `--mixed`( default )`--hard` `--soft`, 508 | 509 | `--hard` 這個參數簡單解釋就是將之前的 commit 都丟掉( 完全 **不保留** )。 510 | 511 | `--soft` 這個參數簡單解釋就是將之前的 commit 都丟掉,但 **保留** 你之前工作區的狀態。 512 | 513 | `--hard` 和 `--soft` 這兩個我覺得用文字不好說明,我建議大家自己可以動手玩玩看,就可以了解他們之間的差異。 514 | 515 | `--soft` 很適合使用在將多個無意義的 commit 合併成一個 commit。 516 | 517 | ![alt tag](https://i.imgur.com/6RVutiK.jpg) 518 | 519 | 版本號 ( ad41df36b7 ) 沒必要全部都寫,寫前幾位就可以了,Git 會自動去找。 520 | 521 | 當你退回到某個版本,突然隔天後悔了,想恢復到之前的新版本該怎麼做呢? 522 | 523 | 找不到新版本的 commit id 該怎麼辦呢? 524 | 525 | 這時候就可以使用一個指令 526 | 527 | ```cmd 528 | git reflog 529 | ``` 530 | 531 | ![alt tag](https://i.imgur.com/MaRlZZr.jpg) 532 | 533 | 接著看你要回到哪個版本,再使用 git reset 即可。 534 | 535 | ```cmd 536 | git reset --hard 642e7af 537 | ``` 538 | 539 | 有時候想消除( 覆蓋 )已經 push 出去的 commit,這時候我們可以使用 540 | 541 | ```cmd 542 | git push --force 543 | ``` 544 | 545 | 或是更簡短的寫法 546 | 547 | ```cmd 548 | git push -f 549 | ``` 550 | 551 | 可以強制 push。先回到某個版本,然後再強制 push。 552 | 553 | ***注意!在多人專案共同開發時,盡量不要用 --force 這種方法,因為有時候會害到別人,建議可以使用 revert 。*** 554 | 555 | 因為上面這個原因,所以建議用另一種比較安全的方式 556 | 557 | ```cmd 558 | git push --force-with-lease 559 | ``` 560 | 561 | 可以確保你沒有隨便丟掉別人的 commit。( 如果有人比你早 commit push 上去,你就會無法 push 到 remote ) 562 | 563 | ## checkout 564 | 565 | 也請參考 [git switch](https://github.com/twtrubiks/Git-Tutorials#git-switch) 和 [git restore](https://github.com/twtrubiks/Git-Tutorials#git-restore). 566 | 567 | `git checkout -- file` 可以丟棄工作區的修改: 568 | 569 | ```cmd 570 | git checkout -- hello.py 571 | ``` 572 | 573 | 命令 git checkout -- hello.py 意思就是,把 hello.py 文件在工作區的修改全部撤銷 ( 丟棄 ) , 574 | 575 | 讓這個檔案回到最近一次 git commit 或 git add 時的狀態。 576 | 577 | ![alt tag](https://i.imgur.com/SrCo4kH.jpg) 578 | 579 | 當然也可以用 git reset 指令直接回到某個 commit。 580 | 581 | ```cmd 582 | git reset --hard xxxxxx 583 | ``` 584 | 585 | ```cmd 586 | git reset --hard 201f40604ec3b6fa8 587 | ``` 588 | 589 | ## 刪除 590 | 591 | 有兩種狀況,一種是確定要從版本庫中刪除該檔案,那就用命令 git rm 刪掉,並且 git commit: 592 | 593 | ```cmd 594 | rm hello.py 595 | git rm hello.py 596 | git commit -m "remove hello.py" 597 | ``` 598 | 599 | ![alt tag](https://i.imgur.com/sLMTDX7.jpg) 600 | 601 | 另一種狀況是刪錯了,使用 git checkout 可以輕鬆還原檔案: 602 | 603 | ```cmd 604 | rm hello.py 605 | git checkout -- hello.py 606 | ``` 607 | 608 | ![alt tag](https://i.imgur.com/5X2NcfS.jpg) 609 | 610 | ## 新建與 合併 ( merge ) 分支 branch 611 | 612 | 在說明分支 branch 之前,先給大家一個觀念。 613 | 614 | 通常開發的時候,大家都是從 **master** 做一個分支 branch 出去,最後再 **merge** 回 master, 615 | 616 | 為什麼要這麼做呢 ? 因為要確保大家都是使用最新的 **master** 617 | 618 | 使用 git branch 指令查看目前的分支: 619 | 620 | ```cmd 621 | git branch 622 | ``` 623 | 624 | ![alt tag](https://i.imgur.com/SVblXD2.jpg) 625 | 626 | 首先創建一個分支,bug1 分支 ( 名稱可以隨便取 ),然後切換到 bug1 分支: 627 | 628 | ```cmd 629 | git branch bug1 630 | git checkout bug1 631 | ``` 632 | 633 | git branch bug1 為創造一個名稱為 bug1 的分支, 634 | 635 | git checkout bug1 為切換到一個名稱為 bug1 的分支底下。 636 | 637 | ![alt tag](https://i.imgur.com/JtGBHk4.jpg) 638 | 639 | 以上兩行指令,相當於下列一行指令 640 | 641 | ```cmd 642 | git checkout -b bug1 643 | ``` 644 | 645 | (這邊教大家一個小技巧, 以下這個指令可以快速切換上一個分支, 和 `cd -` 概念一樣 :exclamation:) 646 | 647 | ```cmd 648 | git checkout - 649 | ``` 650 | 651 | 我們在 bug1 分支上進行任何修改操作, 652 | 653 | 然後再把工作成果 ( 補充一下,修改任何內容後請記得使用 git add 指令和 git commit 指令 ) 合併到 master 分支上: 654 | 655 | ```cmd 656 | git checkout master 657 | git merge bug1 658 | ``` 659 | 660 | ![alt tag](https://i.imgur.com/pF4xDUE.jpg) 661 | 662 | git checkout master 為切換到一個名稱為 master 的分支底下。 663 | 664 | git merge bug1 指令用於合併 ( bug1分支 ) 指定分支到目前分支 ( master ) 底下。 665 | 666 | 如果非常順利, git merge 的訊息裡會出現 Fast-forward,合併速度非常快。 667 | 668 | 當然不是每次合併都能很順利的出現 Fast-forward,很多時候會出現衝突 CONFLICT 。 669 | 670 | 如果順利合併 ( merge ) 完成後,就可以刪除 (本機) bug1 分支: 671 | 672 | ```cmd 673 | git branch -d dev 674 | ``` 675 | 676 | ![alt tag](https://i.imgur.com/LmKKWxR.jpg) 677 | 678 | 如果要丟掉一個沒有被合併過的分支,可以使用 git branch -D 分支名稱 強行刪除 (本機)。 679 | 680 | ```cmd 681 | git branch -D dev 682 | ``` 683 | 684 | 那如果今天要刪除 remote 端的 branch 該怎麼辦呢 :question: 685 | 686 | * [Youtube Tutorial - git 刪除查看遠端的分支 branch](https://youtu.be/0JQrT7nfm_c) 687 | 688 | ```cmd 689 | git push origin --delete {remote_branch} 690 | ``` 691 | 692 | 補充,git branch 也可以修改名稱,而且 commit id 是不會改變的,使用方法也很簡單, 693 | 694 | 可參考 git-branch [文件](https://git-scm.com/docs/git-branch#git-branch--m),使用方法如下, 695 | 696 | ```text 697 | git branch -m 698 | ``` 699 | 700 | 原本的 b1 branch 分支的 log 如下, 701 | 702 | ![alt tag](https://i.imgur.com/b1K1EUy.png) 703 | 704 | 現在將 b1 branch 修改成 b2 branch, 705 | 706 | ![alt tag](https://i.imgur.com/Twz5kRm.png) 707 | 708 | 如果你仔細和剛剛的 log 比較,你會發現 log 的 commit id 是不會改變的, 709 | 710 | ![alt tag](https://i.imgur.com/qMjqV3Z.png) 711 | 712 | ## 使用特定 commit id 建立 branch 713 | 714 | 有時候我們會想測試某個 commit 的狀態, 這時候可以直接利用 commit id 去建立一個 branch, 715 | 716 | 方法如下, 717 | 718 | ```cmd 719 | git checkout -b new_branch 720 | ``` 721 | 722 | 這樣就會依照你指定的 commit id 去建立出一個 branch. 723 | 724 | ## 新建分支 branch 並 push 725 | 726 | 相信大家有時候在 github 上面都會看到,如下圖,很多分支 727 | 728 | ![alt tag](https://i.imgur.com/wrIdlzS.jpg) 729 | 730 | 那我們要如何建立分支呢? 首先,我們先看下面這張圖 731 | 732 | ![alt tag](https://i.imgur.com/3U092a1.jpg) 733 | 734 | 有一個 v1 的分支,並且我在分支上增加一個 g.py 並且 commit。 735 | 736 | 接下來要 **第一次** git push 的時候, 你會發現有錯誤提示 737 | 738 | 請使用以下指令才是正確的 739 | 740 | ```cmd 741 | git push --set-upstream origin v1 742 | ``` 743 | 744 | 也可以使用 745 | 746 | ```cmd 747 | git push -u origin v1 748 | ``` 749 | 750 | 更多詳細說明可參考 [https://git-scm.com/docs/git-push#git-push--u](https://git-scm.com/docs/git-push#git-push--u) 751 | 752 | ![alt tag](https://i.imgur.com/1fuS2VY.jpg) 753 | 754 | 接下來你可以到網頁上看 ( 這裡用 bitbucket 當作範例 ) ,你會發現有分支 v1 了 755 | 756 | ![alt tag](https://i.imgur.com/lOtzsk8.jpg) 757 | 758 | 如果是第一次使用 git clone ,你會發現你只有 master 分支 , 759 | 760 | 這時候我們先查看遠端還有什麼分支, 761 | 762 | ```cmd 763 | git branch -r 764 | ``` 765 | 766 | ```cmd 767 | git branch --remote 768 | ``` 769 | 770 | `--remote` 或 `-r` 都可以. 771 | 772 | 假設遠端有一個名稱為 develop 的分支, 773 | 774 | 我們只要 checkout 到該分支底下就可以了 775 | 776 | ```cmd 777 | git checkout develop 778 | ``` 779 | 780 | ## git switch 781 | 782 | [Youtube Tutorial - git switch 和 git restore 教學](https://youtu.be/JL_bSOGDR-k) 783 | 784 | 請先確認目前的 git 版本, 更新方法可參考 [git 更新](https://github.com/twtrubiks/Git-Tutorials#git-%E6%9B%B4%E6%96%B0). 785 | 786 | 在 git 2.23 版本開始, 增加了 `git switch` 和 `git restore`, 這兩個指令主要是 787 | 788 | 要更清楚的劃分功能, 主要是來代替 `git checkout`. 789 | 790 | 你其實可以想成 `git checkout` = `git switch` + `git restore`. 791 | 792 | 官方文件可參考 [git-switch](https://git-scm.com/docs/git-switch) 793 | 794 | ```cmd 795 | git switch [] (-c|-C) [] 796 | ``` 797 | 798 | 切換到一個已經存在的 branch (如果該 branch 不存在則指令無效) 799 | 800 | ```cmd 801 | git switch 802 | ``` 803 | 804 | 建立 new-branch 並且切換到 new-branch 分支 805 | 806 | ```cmd 807 | git switch -c 808 | ``` 809 | 810 | `-c` `--create` 811 | 812 | `-C` `--force-create` 813 | 814 | 依照 commit_id (或前 N 的 commit 點) 建立 new-branch 並且切換到 new-branch 分支 815 | 816 | ```cmd 817 | git switch -c 818 | git switch -c HEAD~2 819 | ``` 820 | 821 | (這邊教大家一個小技巧, 以下這個指令可以快速切換上一個分支, 和 `cd -` 概念一樣 :smile:) 822 | 823 | ```cmd 824 | git switch - 825 | ``` 826 | 827 | ## git restore 828 | 829 | [Youtube Tutorial - git switch 和 git restore 教學](https://youtu.be/JL_bSOGDR-k) 830 | 831 | 請先確認目前的 git 版本, 更新方法可參考 [git 更新](https://github.com/twtrubiks/Git-Tutorials#git-%E6%9B%B4%E6%96%B0). 832 | 833 | 在 git 2.23 版本開始, 增加了 `git switch` 和 `git restore`, 這兩個指令主要是 834 | 835 | 要更清楚的劃分功能, 主要是來代替 `git checkout`. 836 | 837 | 你其實可以想成 `git checkout` = `git switch` + `git restore`. 838 | 839 | 官方文件可參考 [git-restore](https://git-scm.com/docs/git-restore) 840 | 841 | 以下兩個指令是相同的. 842 | 843 | ```cmd 844 | git checkout 845 | git restore 846 | ``` 847 | 848 | 還原目前資料夾全部的檔案 849 | 850 | ```cmd 851 | git restore . 852 | ``` 853 | 854 | 還原目前資料夾底下結尾是 `*.py` 的全部檔案 855 | 856 | ```cmd 857 | git restore '*.py' 858 | ``` 859 | 860 | 如果你的 `git` 版本比較新, 你應該會發現這個指令你以前好像沒看過 :smile: 861 | 862 | ![alt tag](https://i.imgur.com/IHqfVrn.png) 863 | 864 | ```cmd 865 | git restore --staged 866 | ``` 867 | 868 | ## git pull 869 | 870 | 通常在開始工作或要 push 之前,會先從遠端抓取分支, 871 | 872 | ```cmd 873 | git pull 874 | ``` 875 | 876 | 如果有衝突,要先解衝突。 877 | 878 | 這邊補充一下 `-C` 這個參數的意思, 它的意思代表指定 folder 路徑, 879 | 880 | 有時候我們可能不想先 `cd` 進去資料夾, 再進行 pull, 這時候, 881 | 882 | 就很適合使用它 :smile: 883 | 884 | ```cmd 885 | git [-C ] pull 886 | ``` 887 | 888 | 舉例, 889 | 890 | ```cmd 891 | cd git_folder 892 | git pull 893 | ``` 894 | 895 | 可以直接簡化為 896 | 897 | ```cmd 898 | git -C git_folder pull 899 | ``` 900 | 901 | ## git fetch 902 | 903 | 可以先簡單想成 **git pull = git fetch + git merge** 904 | 905 | 我們先來看下面這張圖, **git fetch + git merge** 906 | 907 | ![alt tag](https://i.imgur.com/COuWByw.png) 908 | 909 | 再看這張圖 **git pull** 910 | 911 | ![alt tag](https://i.imgur.com/8FGuA75.png) 912 | 913 | 這樣是不是清楚多了!!! 914 | 915 | 多補充一個參數 `--prune`, 916 | 917 | * [Youtube Tutorial - git fetch 指令 prune 參數說明](https://youtu.be/ZMpMv1P1Q1Q) 918 | 919 | 這個主要的功能是刪除 remote 無效的 branch, 920 | 921 | 有時候明明已經把遠端的 branch 刪除, 但是你執行 `git branch --remote`, 922 | 923 | 卻會發現你還看的到那些 branch 的分支 (但明明網頁上的分支已經被移除了 :sweat:) 924 | 925 | 常常會發生在 pull 端(非工作端)的機器 (如果不懂這句話的意思建議看影片說明 :smile:) 926 | 927 | 這時候就可以同步一下本機和遠端的分支, 使用以下的指令 928 | 929 | `git fetch --prune` 930 | 931 | ## git rebase 932 | 933 | 什麼是 rebase 呢 ? git rebase 就是避免多餘 ( 沒有意義 ) 的 merge !!! 先看看下面兩張圖 934 | 935 | 補充 : 936 | 937 | ck = checkout 938 | 939 | br = branch 940 | 941 | st = status 942 | 943 | cm = commit 944 | 945 | 可以自行設定。 946 | 947 | 圖一 948 | 949 | ![alt tag](https://i.imgur.com/mWY0f2J.png) 950 | 951 | 圖二 952 | 953 | ![alt tag](https://i.imgur.com/QVZc5P5.png) 954 | 955 | 圖一 和 圖二 你喜歡看哪種圖 ? 答案很明顯,是 圖一 !! 956 | 957 | **rebase** 的目的主要就是盡量讓圖都像 圖一 958 | 959 | 用講的大家一定霧煞煞,所以我直接實戰給大家看。 960 | 961 | 先示範 **沒有使用 rebase** 的範例 962 | 963 | 目前分支 964 | 965 | ![alt tag](https://i.imgur.com/E0ahfnD.png) 966 | 967 | ![alt tag](https://i.imgur.com/Lb4dB0V.png) 968 | 969 | 以上說明 : 先建立 v1 branch,接著 add 後再 commit。 970 | 971 | 假設現在又有人 push 了,以下模擬 pull ,自己加上一個 commit 972 | 973 | ![alt tag](https://i.imgur.com/hFKX4yJ.png) 974 | 975 | 以上說明 : 自己在 master 分支上加 t2.txt , 並且commit ( 模擬 pull ) 976 | 977 | 接下來,切換到 master 分支下和 v1 branch 分支 合併,並且 push 978 | 979 | ![alt tag](https://i.imgur.com/0sCH2Q1.png) 980 | 981 | 你會發現,顯示出來的圖並不漂亮,如下圖 982 | 983 | ![alt tag](https://i.imgur.com/zbIPdyb.png) 984 | 985 | 示範 **使用 rebase** 的範例 986 | 987 | 前面的部份基本上一樣 988 | 989 | ![alt tag](https://i.imgur.com/E0ahfnD.png) 990 | 991 | ![alt tag](https://i.imgur.com/Lb4dB0V.png) 992 | 993 | 以上說明 : 先建立 v1 branch,接著 add 後再 commit。 994 | 995 | 假設現在又有人 push 了,以下模擬 pull ,自己加上一個 commit 996 | 997 | ![alt tag](https://i.imgur.com/hFKX4yJ.png) 998 | 999 | 以上說明 : 自己在 master 分支上加 t2.txt , 並且 commit ( 模擬 pull ) 1000 | 1001 | ***差異的部份*** 1002 | 1003 | ![alt tag](https://i.imgur.com/45ZXGiK.png) 1004 | 1005 | 以上說明 : 先切換到 v1 分支,然後使用以下指令 1006 | 1007 | ```cmd 1008 | git rebase master 1009 | ``` 1010 | 1011 | ![alt tag](https://i.imgur.com/Lpd9Kjr.png) 1012 | 1013 | 以上說明 : 再切回 master 分支,並且使用 merge 合併 v1 分支,最後在 push 1014 | 1015 | 你看~ 是不是變的整齊又漂亮多了呢? 1016 | 1017 | ![alt tag](https://i.imgur.com/1jBI7pw.png) 1018 | 1019 | git rebase 就是將 master 的最新 commit 接回來,再補上自己分支的 commit。 1020 | 1021 | 以上就是 git rebase 的介紹, 1022 | 1023 | 另一種作法, 剛剛是必須切換到 v1 分支底下, 才執行指令, 1024 | 1025 | 如果你現在在任何分支(像是 master 分支), 你可以使用以下指令 1026 | 1027 | ```cmd 1028 | git rebase master v1 1029 | ``` 1030 | 1031 | 就是後面指定 v1, 執行完後它會自動幫你切換到 v1 分支上, 1032 | 1033 | 結果都是一樣的. 1034 | 1035 | 另外, 還有一個指令是 `git rebase --onto` 1036 | 1037 | ```cmd 1038 | git rebase --onto 1039 | ``` 1040 | 1041 | 其實概念上都是一樣的, 就是你想要 rebase 到哪個 new base-commit 上, 1042 | 1043 | 後面放 current base-commit 而已. 1044 | 1045 | 可以搭配 git graph 觀看, 或是看 git 的文檔 `git rebase --help` 1046 | 1047 | ## git rebase interactive 1048 | 1049 | 小弟我當初年輕,一直以為 `git rebase` 就只是讓 commit log 看起來比較乾淨而已,結果無意間發現, 1050 | 1051 | `git rebase` 的 interactive 超強,所以,這邊就來介紹 `git rebase` 的強大功能 :smirk: 1052 | 1053 | 以下是 git rebase interactive 可以使用的指令,這些說明是我從 git 中複製出來的,等等會顯示給大家看, 1054 | 1055 | ```cmd 1056 | # Commands: 1057 | # p, pick = use commit 1058 | # r, reword = use commit, but edit the commit message 1059 | # e, edit = use commit, but stop for amending 1060 | # s, squash = use commit, but meld into previous commit 1061 | # f, fixup = like "squash", but discard this commit's log message 1062 | # x, exec = run command (the rest of the line) using shell 1063 | # d, drop = remove commit 1064 | ``` 1065 | 1066 | 如果大家想要更進一步的了解,請參考 [INTERACTIVE MODE](https://git-scm.com/docs/git-rebase#_interactive_mode), 1067 | 1068 | pick 沒什麼好講的,就使用這個 commit 而已 :smile: 1069 | 1070 | ### reword 1071 | 1072 | [Youtube Tutorial - git rebase interactive - reword - PART 1](https://youtu.be/JhY0rR2wQq0) 1073 | 1074 | ```cmd 1075 | # Commands: 1076 | # p, pick = use commit 1077 | # r, reword = use commit, but edit the commit message 1078 | ``` 1079 | 1080 | 以下為官方的說明 1081 | 1082 | ```txt 1083 | If you just want to edit the commit message for a commit, replace the command "pick" with the command "reword". 1084 | ``` 1085 | 1086 | 說明已經很清楚了,就是可以編輯 commit message。 1087 | 1088 | ( 不能修改 commit 內容,也就是 files 內容 ) 1089 | 1090 | 假設,現在我們有一個 git log 是這樣, 1091 | 1092 | ![alt tag](https://i.imgur.com/6bWnJnK.png) 1093 | 1094 | commit id 2659f65 有 Typo,正確的 commit message 應該是 add c.py 才對, 1095 | 1096 | 所以現在要修正他,我們的目標 commit id 為 2659f65,指令為 1097 | 1098 | ```cmd 1099 | git rebase -i 1100 | ``` 1101 | 1102 | after-this-commit 這個是什麼意思 :question: 1103 | 1104 | 簡單說,就是要選當下的 commit id 的上一個, 1105 | 1106 | 以這個例子來說,我們的目標 commit id 為 2659f65,但指令我們必須下 1107 | 1108 | ```cmd 1109 | git rebase -i f0a761d 1110 | ``` 1111 | 1112 | ![alt tag](https://i.imgur.com/d15nGjx.png) 1113 | 1114 | 這樣應該就很清楚了,總之,記得要選擇目標 commit id 的上一個就對了。 1115 | 1116 | 當你按下 ENTER 之後,你應該會看到下圖 1117 | 1118 | ![alt tag](https://i.imgur.com/4ISGcW1.png) 1119 | 1120 | A 的部份就是我們要修改的目標,B 的部分就是說明 ( 前面貼給大家看的東西 ), 1121 | 1122 | 接著,按 i 進入編輯模式,然後將目標改成 r 或是 reword 都可以,接著輸入 `:wq` 1123 | 1124 | ![alt tag](https://i.imgur.com/zPeHuDa.png) 1125 | 1126 | 接著我們再按下 ENTER,會再跳出一次畫面,這時候,你就將 commit 訊息修改成 1127 | 1128 | 正確的,將 add c.py Typo 修改為 add c.py 1129 | 1130 | ![alt tag](https://i.imgur.com/brYbNqy.png) 1131 | 1132 | 輸入 `:wq` 之後,再 ENTER ( 完成 ) 1133 | 1134 | ![alt tag](https://i.imgur.com/kitKqrm.png) 1135 | 1136 | 我們再用 log 確認一下( 如下圖 ),的確修改成功了,成功將訊息修改為 add c.py, 1137 | 1138 | ![alt tag](https://i.imgur.com/rWojGIu.png) 1139 | 1140 | 這邊有個地方要和大家提一下,就是 commit id 會改變,我把改變的地方框出來給各位看, 1141 | 1142 | 修改前 1143 | 1144 | ![alt tag](https://i.imgur.com/6i6Wv35.png) 1145 | 1146 | 修改後 1147 | 1148 | ![alt tag](https://i.imgur.com/mvj96U2.png) 1149 | 1150 | 簡單來說,就是目前 commit id 之後的 commit id 都會改變 ( 有點繞口 :sweat_smile: ) 1151 | 1152 | 這邊補充一下,只要你用了 rebase,就會看到類似下面的圖, 1153 | 1154 | ![alt tag](https://i.imgur.com/iiDf44q.png) 1155 | 1156 | origin/master 就是指遠端 ( romote ) 的 repo,它是和你說你現在的 repo 已經和 origin/master 1157 | 1158 | 不一樣了,所以,這時候你如果要 push,請使用 `git push --force-with-lease`。 1159 | 1160 | 這邊可能有人會問,如果我希望修改第一個 commit 該怎麼辦 :question: 1161 | 1162 | 這時候可以使用, 1163 | 1164 | ```cmd 1165 | git rebase -i --root 1166 | ``` 1167 | 1168 | ### edit 1169 | 1170 | [Youtube Tutorial - git rebase interactive - edit - PART 2](https://youtu.be/TCKjQppHxxQ) 1171 | 1172 | ```cmd 1173 | # Commands: 1174 | # p, pick = use commit 1175 | # e, edit = use commit, but stop for amending 1176 | ``` 1177 | 1178 | 以下為官方的說明 1179 | 1180 | ```txt 1181 | By replacing the command "pick" with the command "edit", you can tell git rebase to stop after applying that commit, so that you can edit the files and/or the commit message, amend the commit, and continue rebasing. 1182 | ``` 1183 | 1184 | 簡單說,reword 只可以修改 commit message,而 edit 不只可以修改 commit message ,還可以修改 files 內容。 1185 | 1186 | 先來看看下面這張圖 1187 | 1188 | ![alt tag](https://i.imgur.com/9j0JnKw.png) 1189 | 1190 | 這圖很明顯 add a.py -> add b.py -> add c.py -> add d.py ,現在我想在 add c.py 和 add d.py 中再加一個東西, 1191 | 1192 | 也就是變成 add a.py -> add b.py -> add c.py -> add c1.py -> add d.py 這樣。 1193 | 1194 | 增加一個 add c1.py 的情境時就可以使用 edit 了,( 以下我就不說那麼詳細了,我直接講重點 ), 1195 | 1196 | 先執行以下指令 ( 我們的目標是 a7ed6ff ,所以選他的上一個 commit id,也就是 f0a761d ) 1197 | 1198 | ```cmd 1199 | git rebase -i f0a761d 1200 | ``` 1201 | 1202 | 這次我們將 pick 修改成 e 或是 edit ( 如下圖 ) 1203 | 1204 | ![alt tag](https://i.imgur.com/bKrLIl3.png) 1205 | 1206 | 當你按下 ENTER 之後,你會看到下圖, 1207 | 1208 | ![alt tag](https://i.imgur.com/whkCzok.png) 1209 | 1210 | A 的部份是可以修改 commit message, 1211 | 1212 | B 的部份則是和你說當你修改 ( 滿足 ) 完畢,可以執行 `git rebase --continue`, 1213 | 1214 | A 的部份我們不做了,但我們現在來加工吧 ( 增加 c1.py ), 1215 | 1216 | 首先,我們建立一個 c1.py 檔案,然後 `git add c1.py`,接著 commit 他 ( 如下圖 ) 1217 | 1218 | ![alt tag](https://i.imgur.com/frYBUfT.png) 1219 | 1220 | 剛剛有說過了,當你滿足時,可執行 `git rebase --continue`,收工 1221 | 1222 | ![alt tag](https://i.imgur.com/sjnEn0H.png) 1223 | 1224 | 再用 log 確認一下,太神了 :satisfied: 成功加上去了 1225 | 1226 | ![alt tag](https://i.imgur.com/irECwLH.png) 1227 | 1228 | ### squash 1229 | 1230 | [Youtube Tutorial - git rebase interactive - squash fixup - PART 3](https://youtu.be/bfrZrbEHis0) 1231 | 1232 | ```cmd 1233 | # Commands: 1234 | # p, pick = use commit 1235 | # s, squash = use commit, but meld into previous commit 1236 | ``` 1237 | 1238 | 以下為官方的說明 1239 | 1240 | ```text 1241 | The suggested commit message for the folded commit is the concatenation of the commit messages of the first commit and of those with the "squash" command, 1242 | ``` 1243 | 1244 | 簡單說,你如果想要將多個 commit 合併成一個,使用 squash 就對了,( 以下我就不說那麼詳細了,我直接講重點 ), 1245 | 1246 | 這次的目標是要將 commit id fc45824 以及 commit id a7ed6ff 合併起來 ( 如下圖 ) 1247 | 1248 | ![alt tag](https://i.imgur.com/v8XwOTN.png) 1249 | 1250 | 先執行以下指令 1251 | 1252 | ```cmd 1253 | git rebase -i f0a761d 1254 | ``` 1255 | 1256 | 接著你會看到下圖,我們將 fc45824 這個 cmmit 的 pick 修改成 s 或 squash 1257 | 1258 | ( 他會合併他的前一個,也就是 a7ed6ff ) 1259 | 1260 | ![alt tag](https://i.imgur.com/rgWkvVp.png) 1261 | 1262 | ( 如果你要合併多個 commit,就多個都改成 s 或 squash, 注意, 有順序性 :exclamation: :exclamation: ) 1263 | 1264 | 將著按下 ENTER,會看到下圖 1265 | 1266 | ![alt tag](https://i.imgur.com/pB6yllA.png) 1267 | 1268 | 這時候他已經合併了這兩個 commit,我們就可以輸入新的 commit message, 1269 | 1270 | 這邊我們輸入 add c.py and c1.py 1271 | 1272 | ![alt tag](https://i.imgur.com/m9E6KUp.png) 1273 | 1274 | 再按 ENTER ( 成功 ) 1275 | 1276 | ![alt tag](https://i.imgur.com/X0O7I5H.png) 1277 | 1278 | 可以再用 log 確認一下,我們成功將兩個 commit 合併了 1279 | 1280 | ![alt tag](https://i.imgur.com/r53KIev.png) 1281 | 1282 | c.py 以及 c1.py 都存在,代表我們成功了 :satisfied: 1283 | 1284 | ![alt tag](https://i.imgur.com/WhkLDGa.png) 1285 | 1286 | ### fixup 1287 | 1288 | [Youtube Tutorial - git rebase interactive - squash fixup - PART 3](https://youtu.be/bfrZrbEHis0) 1289 | 1290 | ```cmd 1291 | # Commands: 1292 | # p, pick = use commit 1293 | # f, fixup = like "squash", but discard this commit's log message 1294 | ``` 1295 | 1296 | 以下為官方的說明 1297 | 1298 | ```text 1299 | omits the commit messages of commits with the "fixup" command. 1300 | ``` 1301 | 1302 | 其實這個和 squash 很像,通常如果我們要忽略一個 commit message 但保留 commit 的內容,我們就會使用 fixup, 1303 | 1304 | 目標,這邊我們想要移除 fc45824 的個 commit ( 但保留 commit 的內容 ) 1305 | 1306 | ![alt tag](https://i.imgur.com/AFrd0UA.png) 1307 | 1308 | 先執行以下指令 1309 | 1310 | ```cmd 1311 | git rebase -i f0a761d 1312 | ``` 1313 | 1314 | 將 fc45824 的 pick 修改成 f 或 fixup ( 如下圖 ) 1315 | 1316 | ( 他會移除 fc45824 這個 commit message ,但保留 commit 的內容 ) 1317 | 1318 | ![alt tag](https://i.imgur.com/aDH1y1n.png) 1319 | 1320 | 接著 ENTER,成功 rebase 1321 | 1322 | ![alt tag](https://i.imgur.com/BMs2h8r.png) 1323 | 1324 | 可以再用 log 確認一下,我們忽略了 add c1.py 這個 commit 1325 | 1326 | ![alt tag](https://i.imgur.com/bgYJa6T.png) 1327 | 1328 | 但是 c.py 以及 c1.py 都存在 ( 只忽略 commit message ), 1329 | 1330 | ![alt tag](https://i.imgur.com/tYrB3F9.png) 1331 | 1332 | 看到這裡,大家其實可以想一想 squash 和 fixup 真的非常類似, 1333 | 1334 | 只不過 squash 可以修改 commit message。 1335 | 1336 | 簡單一點,單純想要忽略某一個 commit message 時,使用 fixup, 1337 | 1338 | 想要合併 commit 並修改 commit message 時,使用 squash。 1339 | 1340 | ### exec 1341 | 1342 | [Youtube Tutorial - git rebase interactive - exec drop - PART 4](https://youtu.be/u8imRiiSyzk) 1343 | 1344 | ```cmd 1345 | # Commands: 1346 | # p, pick = use commit 1347 | # x, exec = run command (the rest of the line) using shell 1348 | ``` 1349 | 1350 | 以下為官方的說明 1351 | 1352 | ```text 1353 | You may want to check that your history editing did not break anything by running a test, or at least recompiling at intermediate points in history by using the "exec" command (shortcut "x") 1354 | ``` 1355 | 1356 | 這個功能我比較少用,但還是說一下,簡單說,就是他可以用來 check 你的 1357 | 1358 | rebase 改動是不是影響到整體 ( 用 exec command 確認 )。 1359 | 1360 | 聽不太懂 :question: 沒關係,假如我今天做了一大堆的 rabase 更動,但我想確認我這樣做了之後, 1361 | 1362 | 對整體是不是有影響,也就是可以在更動時,順便跑你的 test 去確認整體是正常 work。 1363 | 1364 | 還是聽不懂 :question: 也沒關係,我用一個範例給大家看 1365 | 1366 | ![alt tag](https://i.imgur.com/iu1bEOw.png) 1367 | 1368 | 如上圖,假如我想要在我更動中做一些 test 去確保我的更動不會影響整體, 1369 | 1370 | ( 雖然這邊都是 pick,也就是沒改動,但方便說明,大家請自行想像有改動 :sweat_smile: ) 1371 | 1372 | ![alt tag](https://i.imgur.com/2c9ycmS.png) 1373 | 1374 | A 的部份 echo "test sucess" 這個自然不用有問題, 1375 | 1376 | 但是 B 的部分就會出問題,因為根本沒有 error 這個指令, 1377 | 1378 | 當如果執行到 shell 有錯誤時,他會停下來,讓你修正, 1379 | 1380 | 如下圖,我們停在了 add c.py 這個 commit 上,因為接下來得 test error 了 1381 | 1382 | ![alt tag](https://i.imgur.com/yVB3naC.png) 1383 | 1384 | 這時候我們可以修正問題,修正完了之後,再執行 `git rebase --continue`。 1385 | 1386 | ![alt tag](https://i.imgur.com/YBD0d9V.png) 1387 | 1388 | 這個功能我想應該是讓你去邊修改邊跑你自己的 test,確保改動都正常。 1389 | 1390 | ### drop 1391 | 1392 | [Youtube Tutorial - git rebase interactive - exec drop - PART 4](https://youtu.be/u8imRiiSyzk) 1393 | 1394 | ```cmd 1395 | # Commands: 1396 | # p, pick = use commit 1397 | # d, drop = remove commit 1398 | ``` 1399 | 1400 | 以下為官方的說明 1401 | 1402 | ```text 1403 | To drop a commit, replace the command "pick" with "drop", or just delete the matching line. 1404 | ``` 1405 | 1406 | 這個就簡單多了,移除這個 commit ( 包含 commit 內容 ), 1407 | 1408 | 假設我們的 log 如下, 1409 | 1410 | ![alt tag](https://i.imgur.com/zz5arVp.png) 1411 | 1412 | 這次的目標是移除 f0a761d 和 980bd9a 和 1539219 這些 commit, 1413 | 1414 | 先執行以下指令 1415 | 1416 | ```cmd 1417 | git rebase -i 8f13aaa 1418 | ``` 1419 | 1420 | 將 pick 修改成 d 或 drop ( 如下圖 ) 1421 | 1422 | ![alt tag](https://i.imgur.com/Goc1LH1.png) 1423 | 1424 | 按 ENTER 之後,再用 log 確認一下, 1425 | 1426 | ![alt tag](https://i.imgur.com/u7z2Y3U.png) 1427 | 1428 | 從上圖可以發現,我們已經成功的移除 f0a761d 和 980bd9a 和 1539219 這些 commit, 1429 | 1430 | 並且也看到 commit 內容也都被移除了,只剩下 a.py 而已。 1431 | 1432 | ## git pull 補充 1433 | 1434 | 既然介紹完了 `git fetch` 以及 `git rebase` 之後,接下來我要再補充一些 `git pull` 額外的 options 參數 1435 | 1436 | ```cmd 1437 | git pull [] [ […​]] 1438 | ``` 1439 | 1440 | 更多詳細指令可參考 [https://git-scm.com/docs/git-pull#_options](https://git-scm.com/docs/git-pull#_options)。 1441 | 1442 | 這裡簡單整理一下, 1443 | 1444 | ```cmd 1445 | git pull = git fetch + git merge 1446 | git pull --rebase = git fetch + git rebase 1447 | ``` 1448 | 1449 | 在 [git-rebase](https://github.com/twtrubiks/Git-Tutorials#git-rebase) 中已經讓大家了解到使用 git-rebase 可以讓 code review 的人 1450 | 1451 | 看起來比較舒服,所以就使用 `git pull --rebase` 吧 ( 前提是你要知道你在幹嘛 :smile: )。 1452 | 1453 | 這邊我模擬 `git pull` 以及 `git pull --rebase` 的差異,順便加上衝突的情況,因為步驟蠻多的, 1454 | 1455 | 所以如果你想了解更多他的概念,請參考以下手把手教學, 1456 | 1457 | [Youtube Tutorial - git pull vs git pull --rebase](https://youtu.be/8h0K-2OaeSk) 1458 | 1459 | 使用 `git pull` 後的結果,code review 的人一定翻桌 ( 如下圖 ) :triumph: 1460 | 1461 | 這邊我有順便模擬衝突的時候,你會發現如果使用 `git pull` 會多一個 commit (也就是下方的 "fix conflict")。 1462 | 1463 | ![alt tag](https://i.imgur.com/CNgKR3y.png) 1464 | 1465 | 使用 `git pull --rebase` 後的結果,code review 的人表示溫馨 ( 如下圖 ) :innocent: 1466 | 1467 | 這邊我有順便模擬衝突的時候,你會發現如果使用 `git pull --rebase` 並不會像剛剛一樣多了一個 commit, 1468 | 1469 | 原因是因為當我們使用 `git pull --rebase` 造成衝突時,修好衝突的內容之後,git add xxxx,接著我們會 1470 | 1471 | 直接執行 `git rebase --continue`。 1472 | 1473 | ![alt tag](https://i.imgur.com/RKMo9ue.png) 1474 | 1475 | 假設今天你執行了 `git pull --rebase` 之後,發現很難受 :fearful:,想要取消, 1476 | 1477 | 直接執行 `git rebase --abort` 即可回到之前的狀態。 1478 | 1479 | 額外補充小技巧, 1480 | 1481 | * [Youtube Tutorial - git autostash 參數說明](https://youtu.be/kg2PyZr7l5k) 1482 | 1483 | 說明 `--autostash`, 1484 | 1485 | 一般來說, 如果我們工作到一半, 突然想要直接 `git pull --rebase`, 又不想 commit, 1486 | 1487 | 流程大約會像下面這樣 1488 | 1489 | ```cmd 1490 | git stash # 將目前的改動存進去 stash 中 1491 | git pull --rebase 1492 | git stash pop # 將之前的改動從 stash 中 pop 出來 1493 | # 如果有衝突再去解決衝突 1494 | ``` 1495 | 1496 | 但如果每次都要執行這麼多指令其實會有點煩 :sweat: 1497 | 1498 | 但可以透過一個參數來解決, 也就是 1499 | 1500 | `git pull --rebase --autostash` 1501 | 1502 | 以上這段指令基本上就是幫你執行了剛剛上面那一串的東西, 1503 | 1504 | 如果有衝突, 就再修正衝突即可 :smile: 1505 | 1506 | ## git-cherry-pick 1507 | 1508 | 看影片會更清楚,手把手帶大家動手做 [Youtube Tutorial - git-cherry-pick](https://youtu.be/x3UtKUvlDdI) 1509 | 1510 | git-cherry-pick 這個指令大家可能會比較陌生 :confused: 1511 | 1512 | 沒關係,我們先來看 [官方](https://git-scm.com/docs/git-cherry-pick) 的說明 1513 | 1514 | ```text 1515 | git-cherry-pick - Apply the changes introduced by some existing commits 1516 | ``` 1517 | 1518 | 看完官方說明還是 :question: :question: :question: 1519 | 1520 | 沒關係,我來假設一個情境 ( 理解完它你就了解了 git-cherry-pick 的用途了 ), 1521 | 1522 | 假設現在 master 分支的 log 如下圖 1523 | 1524 | ![alt tag](https://i.imgur.com/cMcn6yE.png) 1525 | 1526 | 然後有一個 v1 的分支 log 如下圖 1527 | 1528 | ![alt tag](https://i.imgur.com/OZ7JLke.png) 1529 | 1530 | 現在我希望 merge v1 分支中的 14dee93 - add d.py 這個 commit 1531 | 1532 | ( 因為 14dee93 這個 commit 實在太棒了或是因為某些原因只需要這個 commit ) 1533 | 1534 | 遇到上述這種情況,就很適合使用 git-cherry-pick,也就是說我想要其他分支中的某幾個 commit 而已, 1535 | 1536 | 不需要全部,換句話說,就是撿其他分支中的 commit 過來使用。 1537 | 1538 | 了解了適合的使用情境,接下來我們就來實戰 :smirk: 1539 | 1540 | 首先,我想要 v1 分支中的 14dee93 - add d.py 這個 commit, 1541 | 1542 | 所以我先切到 master 分支,接著執行 1543 | 1544 | ```cmd 1545 | git cherry-pick 14dee93 1546 | ``` 1547 | 1548 | 如果你想要一次撿很多的分支過來也是可以,直接使用空白隔開即可 1549 | 1550 | ```cmd 1551 | git cherry-pick 14dee93 xxxxxx xxxxxx xxxxxx xxxxx 1552 | ``` 1553 | 1554 | 如果你想一次撿一個區間的 commits, 可以使用以下的指令 1555 | 1556 | ```cmd 1557 | git cherry-pick A^..B 1558 | ``` 1559 | 1560 | (A 和 B 代表你的 commits id) 1561 | 1562 | 如果沒有衝突,就會看到如下圖 1563 | 1564 | ![alt tag](https://i.imgur.com/YITXxMk.png) 1565 | 1566 | 再觀看一下 master 的 log 1567 | 1568 | ![alt tag](https://i.imgur.com/iGEIDZL.png) 1569 | 1570 | 你會發現我們成功把 v1 分支中的 14dee93 - add d.py 這個 commit 拿過來 1571 | 1572 | 使用了,但現在它的 commit id 卻是 ab70429,這個是正常的,因為它需要 1573 | 1574 | 重新新計算 :smile: 1575 | 1576 | 其實,你會發現 git-cherry-pick 沒有想像中的困難 :satisfied: 1577 | 1578 | 在 cherry-pick 時,難免會遇到衝突,這邊我就再多做一個衝突的範例, 1579 | 1580 | 假設 master 的 log 如下 1581 | 1582 | ![alt tag](https://i.imgur.com/pttbQ5U.png) 1583 | 1584 | v1 分支中的 log 如下,我想要它的 3a2f29a - add c.py and print world 這個 commit 1585 | 1586 | ![alt tag](https://i.imgur.com/RFibHS6.png) 1587 | 1588 | v2 分支中的 log 如下,我想要它的 553587b - add f.py 這個 commit 1589 | 1590 | ![alt tag](https://i.imgur.com/I6L2Fwq.png) 1591 | 1592 | 接下來我們就切回 master,然後 cherry-pick 這兩個 commit, 1593 | 1594 | 這時候你會發現,它衝突了 :fearful: 1595 | 1596 | ![alt tag](https://i.imgur.com/fAtQET0.png) 1597 | 1598 | 使用 `git status` 看一下狀態,其實 A 的部分都教你如何解衝突了 1599 | 1600 | ![alt tag](https://i.imgur.com/J8ZpPng.png) 1601 | 1602 | 首先,我們先將 c.py 修正後,執行 `git add c.py`,接著再按照 A 的部份 1603 | 1604 | 執行 `git cherry-pick --continue`,就時候會跳出一個編輯視窗, 1605 | 1606 | ![alt tag](https://i.imgur.com/giylVAL.png) 1607 | 1608 | 輸入完 commit message 之後,再輸入 `wq`,就會看到下圖 1609 | 1610 | ![alt tag](https://i.imgur.com/rA8wMbO.png) 1611 | 1612 | 最後,再觀看 log, 1613 | 1614 | ![alt tag](https://i.imgur.com/lEP648c.png) 1615 | 1616 | 我們成功將我們要的 commit merge 到我們的 master 分支上了 :kissing_smiling_eyes: 1617 | 1618 | 想了解更多的使用方法,可參考官方文件 1619 | [https://git-scm.com/docs/git-cherry-pick](https://git-scm.com/docs/git-cherry-pick)。 1620 | 1621 | ## git revert 1622 | 1623 | 假設我 commit history 為 A1 -> A2 -> A3 -> A4 -> A5 -> A6 1624 | 1625 | 我現在想要回 A4 這個 commit , 這時候我就可以使用 git revert !! 1626 | 1627 | 先 revert A6 1628 | 1629 | ```cmd 1630 | git revert A6 1631 | ``` 1632 | 1633 | 再 revert A5 1634 | 1635 | ```cmd 1636 | git revert A5 1637 | ``` 1638 | 1639 | 假如你再看現在的 commit history , 他會長的像這樣 1640 | 1641 | A1 -> A2 -> A3 -> A4 -> A5 -> A6 -> A6_revert -> A5_revert 1642 | 1643 | 這時候,其實你的 commit 就是在 A4 這個位置 。 1644 | 1645 | 使用 git revert 的好處,就是可以保留 commit history, 萬一你又後悔了, 1646 | 1647 | 也可以在 revert 回去。 1648 | 1649 | 如果你想要 revert 最新的 commit, 只需要使用 HEAD 1650 | 1651 | ```cmd 1652 | git revert HEAD 1653 | ``` 1654 | 1655 | ## 解決衝突 1656 | 1657 | 在進行合併的時候,有時候會顯示出 **衝突conflicts** ,這時候就必須手動解決衝突後再送出。 1658 | 1659 | 通常我目前最容易遇到衝突 conflicts ,就是使用 pull 這個指令的時候 1660 | 1661 | ![alt tag](https://i.imgur.com/Eph0Vw1.jpg) 1662 | 1663 | 仔細看這張圖,如果使用**pull**這個指令,會幫你 **自動 merge** ( 如圖裡的 Auto-merging Hello.py ), 1664 | 1665 | 然後接著看 CONFLICT ( content ) : Merge conflict in Hello.py ,又說 Automatic merge failed, 1666 | 1667 | 就是告訴你, Hello.py 這個檔案有衝突,然後你必須手動下去解決衝突。 1668 | 1669 | git status 可以告訴我們衝突的文件。 1670 | 1671 | ![alt tag](https://i.imgur.com/vlVcXn8.jpg) 1672 | 1673 | 打開衝突文件我們會看到 Git 用 <<<<<<<,=======,>>>>>>> 標記出不同分支的內容,我們修改完畢後再提交: 1674 | 1675 | ![alt tag](https://i.imgur.com/rlPOaxn.jpg) 1676 | 1677 | 通常我們會手動下去修改衝突 conflicts,然後再加個 commit 1678 | 1679 | ```cmd 1680 | git add Hello.py 1681 | git commit -m "conflict fixed" 1682 | ``` 1683 | 1684 | ### 假設今天我們想要放棄這個 merge 我們該怎麼做呢 ? 1685 | 1686 | ```cmd 1687 | git merge --abort 1688 | ``` 1689 | 1690 | 或 1691 | 1692 | ```cmd 1693 | git reset --hard HEAD 1694 | ``` 1695 | 1696 | 可以取消這次的 merge 回到 merge 前。 1697 | 1698 | ## git stash 指令 1699 | 1700 | * [Youtube Tutorial - git stash 指令](https://youtu.be/CN065MNHtMY) 1701 | 1702 | 很多時候,我們正在開發一個新功能又或是 debug,然後突然有一個功能需要緊急修正, 1703 | 1704 | 但你又不想 commit 現在的狀況,因為根本沒意義,事情只做了一半,這時候 **stash** 1705 | 1706 | 這個實用的指令就派上用場了。 1707 | 1708 | 舉個例子,假設我們改了 A.py 和 B.py 這兩個檔案 1709 | 1710 | ![alt tag](https://i.imgur.com/7xX0T1T.jpg) 1711 | 1712 | 然後,現在突然有一個 bug 必須馬上(立刻)處理, 1713 | 1714 | 但是,啊我手上的事情還沒做完阿~~~~ 1715 | 1716 | 這時候,可以利用以下指令 1717 | 1718 | ```cmd 1719 | git stash 1720 | ``` 1721 | 1722 | ![alt tag](https://i.imgur.com/cYCH8mV.jpg) 1723 | 1724 | 假如你想要更清楚自己這次的 stash 原因是什麼,或是這是正在開發什麼功能 1725 | 可以使用以下指令 1726 | 1727 | 範例 1728 | 1729 | ```cmd 1730 | git stash save "我是註解" 1731 | ``` 1732 | 1733 | ```cmd 1734 | git stash save -u "feature" 1735 | ``` 1736 | 1737 | 參數說明 1738 | 1739 | `-u` | `--include-untracked` 1740 | 1741 | `-a` | `--all` 1742 | 1743 | ![alt tag](https://i.imgur.com/nGS11Px.jpg) 1744 | 1745 | 接下來你可以使用 status 指令,你會發現變乾淨了 1746 | 1747 | ![alt tag](https://i.imgur.com/Xf53GfM.jpg) 1748 | 1749 | 並且可以使用下列的指令來觀看 stash 裡面的東西 1750 | 1751 | ```cmd 1752 | git stash list 1753 | ``` 1754 | 1755 | ![alt tag](https://i.imgur.com/jQPiYiX.jpg) 1756 | 1757 | 然後你很努力地解決這個 bug,commit 完之後, 1758 | 可以再使用下列的指令把 stash 取回來,這指令取回後也會刪除 stash 1759 | 1760 | ```cmd 1761 | git stash pop 1762 | ``` 1763 | 1764 | 假設今天你有很多的 stash,你可以指定,如下 (選自己喜歡的用法) 1765 | 1766 | ```cmd 1767 | git stash pop 0 1768 | git stash pop stash@{0} 1769 | ``` 1770 | 1771 | ![alt tag](https://i.imgur.com/zVF7no2.jpg) 1772 | 1773 | 你會發現剛剛的東西回來了~ 1774 | 1775 | 如果你希望使用 stash 取回之後,不希望刪除 stash ,可以使用下列的指令 1776 | 1777 | ```cmd 1778 | git stash apply 1779 | ``` 1780 | 1781 | 如下圖,你可以發現取回後, stash 並沒有被刪除 1782 | 1783 | ![alt tag](https://i.imgur.com/w3Ip3iW.jpg) 1784 | 1785 | 如果你只是想要刪除暫存,可以使用下列的指令 1786 | 1787 | ```cmd 1788 | git stash clear 1789 | ``` 1790 | 1791 | 從下圖可以發現,stash 裡面的東西被我們刪除了 1792 | 1793 | ![alt tag](https://i.imgur.com/PvzufbQ.jpg) 1794 | 1795 | 如果你想丟棄指定的 stash,可以使用 (選自己喜歡的用法) 1796 | 1797 | ```cmd 1798 | git stash drop 0 1799 | git stash drop stash@{0} 1800 | ``` 1801 | 1802 | ## git tag 1803 | 1804 | [Youtube Tutorial - git tag 教學](https://youtu.be/azciLlpr3Gs) 1805 | 1806 | 查看 tag 1807 | 1808 | ```cmd 1809 | git tag 1810 | ``` 1811 | 1812 | ![alt tag](https://i.imgur.com/8f6zGfm.png) 1813 | 1814 | 指定關鍵字 1815 | 1816 | ```cmd 1817 | git tag -l "v1.*" 1818 | ``` 1819 | 1820 | `-l` `--list` 1821 | 1822 | git tag 有 輕量級標籤(lightweight tag) 和 附註標籤(annotated tag). 1823 | 1824 | 輕量級標籤(lightweight tag) 1825 | 1826 | 如果想要建立一個輕量級的標籤,請不要指定 `-a` `-s`(GPG-signed) `-m` 1827 | 1828 | ```cmd 1829 | git tag tag_name [commit_id] 1830 | ``` 1831 | 1832 | 如果只使用 `git tag tag_name` , 而沒加上後面 Commit id, 1833 | 1834 | 則會自動把 tag 放在目前的這個 Commit id 上. 1835 | 1836 | 顯示註解 1837 | 1838 | ```cmd 1839 | git show v1.1-light 1840 | ``` 1841 | 1842 | 附註標籤(annotated tag) 1843 | 1844 | ```cmd 1845 | git tag -a v1.1 -m "version 1.1" 1846 | ``` 1847 | 1848 | `-a` 就是標籤名稱 `--annotate` 1849 | 1850 | `-m` 代表該標籤說明(註解) 1851 | 1852 | 在指定的 commit 上設 tag 1853 | 1854 | ```cmd 1855 | git tag -a v1.2 -m "version 1.1" [commit_id] 1856 | ``` 1857 | 1858 | 顯示註解 1859 | 1860 | ```cmd 1861 | git show v1.1 1862 | ``` 1863 | 1864 | 輕量級標籤(lightweight tag) 和 附註標籤(annotated tag) 的差別就是是否能看到更多的細節, 1865 | 1866 | 附註標籤(annotated tag) 多了更多的資訊. 1867 | 1868 | 輕量級標籤(lightweight tag) 如下 1869 | 1870 | ![alt tag](https://i.imgur.com/4cUkdyQ.png) 1871 | 1872 | 附註標籤(annotated tag) 如下 1873 | 1874 | ![alt tag](https://i.imgur.com/DQWB1uh.png) 1875 | 1876 | 當你執行 `git push` 預設是不會將 tag 推到 remote. 1877 | 1878 | 需要執行以下的指令, push tag 到 remote 端 1879 | 1880 | ```cmd 1881 | git push origin [tagname] 1882 | ``` 1883 | 1884 | 一次 push 很多 tags (將會把你全部不在 remote 端的 tag 都 push 上去.) 1885 | 1886 | ```cmd 1887 | git push origin --tags 1888 | ``` 1889 | 1890 | 當其他人執行 `git clone` 或 `git fetch` 就可以拿到這些 tags. 1891 | 1892 | 移除本地 tag 1893 | 1894 | ```cmd 1895 | git tag -d [tagname] 1896 | ``` 1897 | 1898 | 刪除 remote tag 1899 | 1900 | ```cmd 1901 | git push --delete origin [tagname] 1902 | ``` 1903 | 1904 | ## git show 1905 | 1906 | 一般來說,我只用他來看這個 commit 修改了哪些東西 1907 | 1908 | ```cmd 1909 | git show 1910 | ``` 1911 | 1912 | ![alt tag](https://i.imgur.com/rjpl8VL.png) 1913 | 1914 | ```cmd 1915 | git show [] […​] 1916 | ``` 1917 | 1918 | 其他更詳細的介紹,請參考 [https://git-scm.com/docs/git-show](https://git-scm.com/docs/git-show) 1919 | 1920 | ## git diff 1921 | 1922 | 以下為官方說明 1923 | 1924 | ```text 1925 | Show changes between commits, commit and working tree, etc 1926 | ``` 1927 | 1928 | 這邊舉幾個例子, 1929 | 1930 | 檔案還沒進入暫存區 ( Stage ),也就是執行 git add xxx 之前, 1931 | 1932 | 可以看做了那些修改, 1933 | 1934 | ![alt tag](https://i.imgur.com/nj5Gz5P.png) 1935 | 1936 | 也可以看 commits 之間的差異 1937 | 1938 | ![alt tag](https://i.imgur.com/JMJ48jO.png) 1939 | 1940 | 其他更詳細的介紹,請參考 [https://git-scm.com/docs/git-diff](https://git-scm.com/docs/git-diff) 1941 | 1942 | ## git diff-tree 1943 | 1944 | git-diff-tree - Compares the content and mode of blobs found via two tree objects 1945 | 1946 | 比較兩個 blob (commit) 的差異. 1947 | 1948 | 文件可參考 [git-diff-tree](https://git-scm.com/docs/git-diff-tree/en) 1949 | 1950 | 直接看範例 1951 | 1952 | ```cmd 1953 | git diff-tree -r --no-commit-id --name-status -a --diff-filter=ACDMRT <其中一個commit id> <要比較的commit-id> > changes.txt 1954 | ``` 1955 | 1956 | `-r` 代表 Recurse into sub-trees. 1957 | 1958 | `--no-commit-id` This flag suppressed the commit ID output. 1959 | 1960 | `--name-status` Show only the name(s) and status of each changed file. 1961 | 1962 | `--text` `-a` Treat all files as text. 1963 | 1964 | `--diff-filter=[(A|C|D|M|R|T|U|X|B)…​[*]]` 1965 | 1966 | Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R), 1967 | 1968 | have their type (i.e. regular file, symlink, submodule, …​) 1969 | 1970 | changed (T), are Unmerged (U), are Unknown (X), 1971 | 1972 | or have had their pairing Broken (B). 1973 | 1974 | 執行後打開 change.txt 會看到差異的檔案名稱. 1975 | 1976 | ```text 1977 | M addons/account/i18n/account.pot 1978 | M addons/account/i18n/ar.po 1979 | M addons/account/i18n/az.po 1980 | M addons/account/i18n/be.po 1981 | M addons/account/i18n/bg.po 1982 | M addons/account/i18n/ca.po 1983 | M addons/account/i18n/cs.po 1984 | ...... 1985 | ``` 1986 | 1987 | ## git archive 1988 | 1989 | 延續上面的例子, 如果想要打包不同的 commit 之間的差異檔案 (不想要整包匯出, 因為太大了, 只想找出差異檔案), 1990 | 1991 | 這時候可以搭配 archive 指令, 範例如下 1992 | 1993 | ```cmd 1994 | git archive --format=zip --output=files-diff.zip HEAD $(git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT <其中一個commit id> <要比較的commit-id>) 1995 | ``` 1996 | 1997 | 這樣匯出來的 zip, 就是這兩個 commit 之間差異的完整檔案. 1998 | 1999 | ## git grep 2000 | 2001 | 以下為官方說明 2002 | 2003 | ```text 2004 | git-grep - Print lines matching a pattern 2005 | ``` 2006 | 2007 | 簡單說,就是可以幫你找出符合的 pattern,舉個例子,我希望找出內容 2008 | 2009 | 有包含 hello 這個 pattern 的檔案,這時候,就可以執行以下指令 2010 | 2011 | ```cmd 2012 | git grep "hello" 2013 | ``` 2014 | 2015 | ![alt tag](https://i.imgur.com/t5vxvvp.png) 2016 | 2017 | 會顯示出該 pattern 在個檔案以及哪段程式碼有用到。 2018 | 2019 | 其他更詳細的介紹,請參考 [https://git-scm.com/docs/git-grep](https://git-scm.com/docs/git-grep) 2020 | 2021 | ## git clean 2022 | 2023 | 刪除未被追蹤的檔案, 2024 | 2025 | `git clean -n` 2026 | 2027 | `-n, --dry-run` Don’t actually remove anything, just show what would be done 2028 | 2029 | 這個指定是告訴你會刪除哪些資料, 不會真的刪除. 2030 | 2031 | 範例如下, 2032 | 2033 | ```cmd 2034 | ❯ git status 2035 | On branch master 2036 | 2037 | No commits yet 2038 | 2039 | Untracked files: 2040 | (use "git add ..." to include in what will be committed) 2041 | test.py 2042 | 2043 | nothing added to commit but untracked files present (use "git add" to track) 2044 | 2045 | ❯ git clean -n 2046 | Would remove test.py 2047 | ``` 2048 | 2049 | 如果你執行以下的指令, 就會真的刪除, 2050 | 2051 | `git clean -df` 2052 | 2053 | 詳細說明可使用 `git clean --help` 觀看, 2054 | 2055 | 範例如下, 2056 | 2057 | ```cmd 2058 | ❯ git status 2059 | On branch master 2060 | 2061 | No commits yet 2062 | 2063 | Untracked files: 2064 | (use "git add ..." to include in what will be committed) 2065 | test.py 2066 | 2067 | nothing added to commit but untracked files present (use "git add" to track) 2068 | 2069 | ❯ git clean -df 2070 | Removing test.py 2071 | 2072 | ❯ git status 2073 | On branch master 2074 | 2075 | No commits yet 2076 | 2077 | nothing to commit (create/copy files and use "git add" to track) 2078 | ``` 2079 | 2080 | 還記得前面介紹的 `git reset` 指令嗎, 基本上它可以搭配 `git clean` 一起使用, 2081 | 2082 | `git clean` 影響沒有被 track 的檔案 2083 | 2084 | `git reset` 影響有被 track 的檔案 2085 | 2086 | 結合以上, 可以回到一個指定的 commit 乾淨的狀態, 2087 | 2088 | ```cmd 2089 | git reset --hard HEAD 2090 | git clean -df 2091 | git status 2092 | ``` 2093 | 2094 | 建議大家自己操作一下. 2095 | 2096 | ## git Submodule 2097 | 2098 | 由於這個內容稍微比較多,所以我另外寫了一篇, 2099 | 2100 | * [Youtube Tutorial PART 1 - git Submodule tutorial - how to create submodule](https://youtu.be/IDMWLJCbCGo) 2101 | 2102 | * [Youtube Tutorial PART 2 - git Submodule tutorial - how to update submodule](https://youtu.be/ogZoZOVyAYI) 2103 | 2104 | * [Youtube Tutorial PART 3 - git Submodule tutorial - how to clone submodule](https://youtu.be/f5_O5Iu6pJo) 2105 | 2106 | * [Youtube Tutorial PART 4 - git Submodule tutorial - how to remove submodule](https://youtu.be/imndFN7AvFA) 2107 | 2108 | [git Submodule tutorial :memo:](https://github.com/twtrubiks/Git-Tutorials/blob/master/git_submodule_turorial.md) 2109 | 2110 | ## git Subtree 2111 | 2112 | 由於這個內容稍微比較多,所以我另外寫了一篇, 2113 | 2114 | * [Youtube Tutorial PART 1 - git subtree tutorial - how to create subtree](https://youtu.be/kEvgK2gH_vg) 2115 | 2116 | * [Youtube Tutorial PART 2 - git subtree tutorial - how to push subtree](https://youtu.be/Df3zc1VOqN8) 2117 | 2118 | * [Youtube Tutorial PART 3 - git subtree tutorial - how to pull/create subtree](https://youtu.be/dE-D2yrD4ws) 2119 | 2120 | [git subtree tutorial :memo:](https://github.com/twtrubiks/Git-Tutorials/blob/master/git_subtree_turorial.md) 2121 | 2122 | ## git 其他設定 2123 | 2124 | 我們已經設定了 user.name 以及 user.email ,但 Git 上其實還有很多可設定的東西 2125 | 2126 | 有時候,我們必須把某些檔案 ( 文件夾 ) 放到 Git 工作目錄中,但又不能提交它們, 2127 | 2128 | 像是密碼設定或是編譯器 IDE 產生出來的東西之類的, 2129 | 2130 | 每次 git status 都會看到紅紅的 Untracked files ,通常會覺得有點煩...... 2131 | 2132 | 這問題 Git 也幫我們想過,只要在 Git 工作區的根目錄下新建一個特殊的 **.gitignore** 文件 , 2133 | 2134 | 然後把要忽略的文件 ( 檔案 ) 名稱輸入進去, Git 就會自動忽略這些文件。 2135 | 2136 | 當然不需要自己從頭寫 .gitignore 文件, GitHub 已經幫我們準備了一些文件 [gitignore](https://github.com/github/gitignore) 2137 | 2138 | **.gitignore** 檔案直接放在目錄底下即可 2139 | 2140 | ![alt tag](https://i.imgur.com/8rHPsII.jpg) 2141 | 2142 | ### .gitignore 檔案格式範例 2143 | 2144 | ![alt tag](https://i.imgur.com/W3cxk9r.jpg) 2145 | 2146 | ### .gitignore (Temporarily and Permanently) 2147 | 2148 | 主要分 暫時(Temporarily) 和 永久(Permanently) 的ignore, 2149 | 2150 | * Temporarily ignore 2151 | 2152 | 適合使用在 settings 的檔案,有時候我們在開發的時候,都會有自己的設定, 2153 | 2154 | 但這個設定未必是大家都需要的,這時候就可以暫時先忽略這個檔案的改變。 2155 | 2156 | 暫時忽略某個檔案 2157 | 2158 | ```cmd 2159 | git update-index --skip-worktree 2160 | ``` 2161 | 2162 | 恢復(Resume)暫時忽略某個檔案 2163 | 2164 | ```cmd 2165 | git update-index --no-skip-worktree 2166 | ``` 2167 | 2168 | * Permanently ignore 2169 | 2170 | 這邊補充一個情境,假設今天 file 這個檔案已經被 commit 到 git 中了, 2171 | 2172 | 但是我想把他加入 .gitignore,這樣該怎麼辦 :question: 2173 | 2174 | 如果你在 .gitignore 中加入 file,你會發現還是沒有被 ignore :confused: 2175 | 2176 | ![alt tag](https://i.imgur.com/o922paa.png) 2177 | 2178 | 這時候,正確的做法應該是要先執行已下指令, 2179 | 2180 | ```cmd 2181 | git rm --cached 2182 | ``` 2183 | 2184 | 執行完後再 commit 即可 ( 檔案不會從系統上刪除,只是要更新 git 的 index 而已 ) 2185 | 2186 | ![alt tag](https://i.imgur.com/RJZ08OQ.png) 2187 | 2188 | 這時候可以再嘗試更新 file 的內容,你會發現它成功被 ignore 了 :smile: 2189 | 2190 | ### git alias 2191 | 2192 | 有時候常常手殘 key 錯指令或是記不起來 2193 | 2194 | 如果我們打 git st 就表示 git status 那該有多棒!!! 2195 | 2196 | 所以我們可以自己設定,讓 Git 以後打 **git st = git status** 2197 | 如下圖,原本不能使用 git st ,設定完之後就可以使用了。 2198 | 2199 | ```cmd 2200 | git config --global alias.st status 2201 | ``` 2202 | 2203 | ![alt tag](https://i.imgur.com/4NNasgB.jpg) 2204 | 2205 | ```cmd 2206 | git config --global alias.br branch 2207 | ``` 2208 | 2209 | ![alt tag](https://i.imgur.com/NIc71AO.jpg) 2210 | 2211 | ```cmd 2212 | git config --global alias.ck checkout 2213 | ``` 2214 | 2215 | ```cmd 2216 | git config --global alias.sw switch 2217 | ``` 2218 | 2219 | ```cmd 2220 | git config --global alias.cm commit 2221 | ``` 2222 | 2223 | ```cmd 2224 | git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative" 2225 | ``` 2226 | 2227 | 將前面這一大串變成一個別名,這樣以後只需要執行 `git lg` 即可, 2228 | 2229 | ![alt tag](https://i.imgur.com/IvQLsMR.png) 2230 | 2231 | 可能有人會問,那這個設定檔文件在哪裡呢? 2232 | 2233 | 通常會在你的使用者底下,例如我這台電腦使用者為 HJ,設定檔文件就會在 **C:\Users\HJ** 底下, 2234 | 2235 | 他是一個 **隱藏文件.gitconfig** ,打開他的話格式如下。 2236 | 2237 | ![alt tag](https://i.imgur.com/iXjIqv9.jpg) 2238 | 2239 | 不知道大家有沒有注意到 `--global` 這個參數,他代表的意思是全域的,如果說你今天是執行 2240 | 2241 | ```cmd 2242 | git config alias.stu status 2243 | ``` 2244 | 2245 | 代表只有在該目錄底下時才會有作用。 2246 | 2247 | 那這個有什麼用呢? 試想一種情境,假設你在特定的資料夾底下,想要使用特定的信箱去 push,而其他的資料夾, 2248 | 2249 | 則一樣使用公司的信箱,這時候,就非常適合使用這種方法完成。 2250 | 2251 | 更多資訊細節可使用以下命令查看 2252 | 2253 | ```cmd 2254 | man git-config 2255 | ``` 2256 | 2257 | ### git 更新 2258 | 2259 | ```cmd 2260 | sudo add-apt-repository ppa:git-core/ppa 2261 | sudo apt-get update 2262 | sudo apt-get install git 2263 | ``` 2264 | 2265 | ![alt tag](https://i.imgur.com/WrQNZln.png) 2266 | 2267 | ## 使用 Git 一次 Push 到多個不同的遠端 ( remote ) 2268 | 2269 | 假如有一天 github 掛了,這樣是不是就不能 work 了,你可能會說本地端還有 ? 2270 | 2271 | 但......多備份絕對是好事 !! 再這裡介紹如何一次 Push 到多個不同的遠端 ( remote ) 2272 | 2273 | 這裡用 [Bitbucket](https://bitbucket.org/product) 當作範例 2274 | 2275 | 先使用下方指令查看 2276 | 2277 | ```cmd 2278 | git remote -v 2279 | ``` 2280 | 2281 | ![alt tag](https://i.imgur.com/Qb5VHoP.png) 2282 | 2283 | git remote 這個指令的更多說明可參考官方文件 [git-remote](https://git-scm.com/docs/git-remote)。 2284 | 2285 | 接著我們使用下列指令新增一個 origin 的遠端 2286 | 2287 | ```cmd 2288 | git remote set-url --add origin 2289 | ``` 2290 | 2291 | ```cmd 2292 | git remote set-url --add origin git@github.com:twtrubiks/test2.git 2293 | ``` 2294 | 2295 | ![alt tag](https://i.imgur.com/FKzexVE.png) 2296 | 2297 | 我們再用 git remote -v 查看一次,你會發現多了剛剛新增的遠端 ( remote ) 2298 | 2299 | ![alt tag](https://i.imgur.com/p1q7C4b.png) 2300 | 2301 | 最後我們再 push 2302 | 2303 | ![alt tag](https://i.imgur.com/6VKh8Bz.png) 2304 | 2305 | 仔細看,是不是一次 push 到多個不同的遠端 ( remote ),非常方便!! 2306 | 2307 | ***GitHub*** 2308 | 2309 | ![alt tag](https://i.imgur.com/JljPJHJ.png) 2310 | 2311 | ***Bitbucket*** 2312 | 2313 | ![alt tag](https://i.imgur.com/rkYHNl4.png) 2314 | 2315 | P.S 設定檔在資料夾底下的隱藏檔 ".git" 底下,裡面有一個 config 2316 | 2317 | ![alt tag](https://i.imgur.com/41xb8eu.png) 2318 | 2319 | 補充幾個 git remote 的指令,他也支援 rename 以及 remove , 2320 | 2321 | 現在的 remote 如下, 2322 | 2323 | ![alt tag](https://i.imgur.com/rr9SE3g.png) 2324 | 2325 | 讓我們重新命名 remote,語法如下, 2326 | 2327 | ```text 2328 | git remote rename 2329 | ``` 2330 | 2331 | ```text 2332 | git remote rename origin2 origin 2333 | ``` 2334 | 2335 | 執行後,你會發現 remote 成功被修改成 origin 了, 2336 | 2337 | ![alt tag](https://i.imgur.com/ixP1H7Z.png) 2338 | 2339 | 接下來我們試試 remove,語法如下, 2340 | 2341 | ```text 2342 | git remote remove 2343 | ``` 2344 | 2345 | ```text 2346 | git remote remove origin 2347 | ``` 2348 | 2349 | 成功刪除,現在 remote 是空的了, 2350 | 2351 | ![alt tag](https://i.imgur.com/OQFRWDg.png) 2352 | 2353 | 接下來我們嘗試新增一個 remote,指令如下, 2354 | 2355 | ```text 2356 | git remote add [-t ] [-m ] [-f] [--[no-]tags] [--mirror=] 2357 | ``` 2358 | 2359 | ```cmd 2360 | git remote add origin git@github.com:blue-rubiks/t11.git 2361 | ``` 2362 | 2363 | ![alt tag](https://i.imgur.com/cKsiBBs.png) 2364 | 2365 | 如果我們想修改 origin 的 url,可以使用 2366 | 2367 | ```cmd 2368 | git remote set-url origin git@blue.github.com:blue-rubiks/t11.git 2369 | ``` 2370 | 2371 | ![alt tag](https://i.imgur.com/LJICTNM.png) 2372 | 2373 | ## Multiple SSH Keys settings for different github account 2374 | 2375 | * [Youtube Tutorial - Multiple SSH Keys settings for different github account](https://youtu.be/gDxG-4tF7B8) 2376 | 2377 | [Multiple SSH Keys settings for different github account](https://github.com/twtrubiks/Git-Tutorials/blob/master/Multiple_SSH_Keys_settings.md) 2378 | 2379 | ## Git-Flow 基本教學以及概念 2380 | 2381 | * [Git-Flow Tutorials - youtube](https://youtu.be/zXlta66thZY) 2382 | 2383 | * [Git-Flow SmartGit Tutorials - youtube](https://youtu.be/ualXHytifbg) 2384 | 2385 | [Git-Flow 基本教學以及概念](https://github.com/twtrubiks/Git-Tutorials/tree/master/Git-Flow) 2386 | 2387 | ## PR (Pull Request) 教學 2388 | 2389 | * [Youtube Tutorial - github PR (Pull Request) 教學](https://youtu.be/bXOdD-bKfkA) - [文章快速連結](https://github.com/twtrubiks/Git-Tutorials/tree/master/pr-tutorial#github-pr-pull-request-%E6%95%99%E5%AD%B8) 2390 | 2391 | * [Youtube Tutorial - github CLI PR 教學 - gh](https://youtu.be/AD8X11lq3gQ) - [文章快速連結](https://github.com/twtrubiks/Git-Tutorials/tree/master/pr-tutorial#github-cli-pr-%E6%95%99%E5%AD%B8) 2392 | 2393 | [PR (Pull Request) 教學](https://github.com/twtrubiks/Git-Tutorials/tree/master/pr-tutorial) 2394 | 2395 | ## Linux 注意事項 2396 | 2397 | * [Youtube Tutorial - Linux 教學 - git 乎略 file mode (chmod) 改變](https://youtu.be/QCh2k903Yak) 2398 | 2399 | 這邊是和大家說一些同時在 windows 以及 linux 底下使用 git 可能會遇到的問題. 2400 | 2401 | 首先, 在 linux 底下執行以下指令 2402 | 2403 | ```cmd 2404 | sudo chmod -R 777 folder 2405 | ``` 2406 | 2407 | git 會默認它為改變, 要怎麼把它忽略呢 ? 請執行以下指令 , 2408 | 2409 | ```cmd 2410 | git config core.fileMode false 2411 | ``` 2412 | 2413 | 也可參考這篇文章 [Git ignore file mode (chmod) changes](https://stackoverflow.com/questions/1580596/how-do-i-make-git-ignore-file-mode-chmod-changes) 2414 | 2415 | ### 格式化 2416 | 2417 | `core.autocrlf` 2418 | 2419 | Windows 使用 Enter (Carriage Return 簡寫為 CR) 和 換行(Line Feed 簡寫為 LF) 這兩個字元來定義換行, 2420 | 2421 | 而 Mac 和 Linux 只使用一個換行 (Line Feed 簡寫為 LF) 字元. 2422 | 2423 | 所以會導致跨平台協作時出問題. 2424 | 2425 | 在 windows 上可以這樣設定 ( 代表 LF 會被轉換成 CRLF) 2426 | 2427 | ```cmd 2428 | git config --global core.autocrlf true 2429 | ``` 2430 | 2431 | Linux 或 Mac 系統 2432 | 2433 | ```cmd 2434 | git config --global core.autocrlf input 2435 | ``` 2436 | 2437 | 以上這樣設定, 會在 Windows 上保留 CRLF,而在 Mac 和 Linux 以及 repo 中保留 LF. 2438 | 2439 | 如果你想更深入的了解, 可參考 [格式化-core.autocrlf](https://git-scm.com/book/zh-tw/v1/Git-客製化-Git-設定#格式化與空格). 2440 | 2441 | ### 修改 editor 2442 | 2443 | ```cmd 2444 | git config --global core.editor "vim" 2445 | ``` 2446 | 2447 | ## Reference 2448 | 2449 | * [13 Git tips for Git's 13th birthday](https://opensource.com/article/18/4/git-tips) 2450 | 2451 | ## Donation 2452 | 2453 | 文章都是我自己研究內化後原創,如果有幫助到您,也想鼓勵我的話,歡迎請我喝一杯咖啡 :laughing: 2454 | 2455 | 綠界科技ECPAY ( 不需註冊會員 ) 2456 | 2457 | ![alt tag](https://payment.ecpay.com.tw/Upload/QRCode/201906/QRCode_672351b8-5ab3-42dd-9c7c-c24c3e6a10a0.png) 2458 | 2459 | [贊助者付款](http://bit.ly/2F7Jrha) 2460 | 2461 | 歐付寶 ( 需註冊會員 ) 2462 | 2463 | ![alt tag](https://i.imgur.com/LRct9xa.png) 2464 | 2465 | [贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8) 2466 | 2467 | ## 贊助名單 2468 | 2469 | [贊助名單](https://github.com/twtrubiks/Thank-you-for-donate) 2470 | -------------------------------------------------------------------------------- /git_submodule_turorial.md: -------------------------------------------------------------------------------- 1 | # git Submodule tutorial :memo: 2 | 3 | 手把手帶大家動手做 [Youtube Tutorial PART 1 - git Submodule tutorial - how to create submodule](https://youtu.be/IDMWLJCbCGo), 4 | 5 | git Submodule 的官方文件可參考 [git-submodule](https://git-scm.com/docs/git-submodule/)。 6 | 7 | 在開始介紹前,我們先來談談何時會使用到 git Submodule, 8 | 9 | 大家一定有遇過這種情境,就是 A repo 依賴 B repo,這邊你可能會問我, 10 | 11 | 阿為什麼不直接把兩個 repo 變成一個 repo 就好呢 :question: 12 | 13 | 這樣不是增加遊戲困難度 :question: 14 | 15 | 有時候,這個 B repo 可能是 github 上一個很多人維護超強的東西, 16 | 17 | 你不可能自己維護,更何況,不要重造輪子,我們要站上巨人的肩膀上 :kissing_smiling_eyes: 18 | 19 | 好,現在我們拉回來,當每次我們發現 B repo 有更新時,我們以前的做法 20 | 21 | 可能就要單獨把他 clone 下來,然後在複製進我們的 A repo 中, 22 | 23 | 但其實這樣真的太土炮,而且非常沒有效率 :expressionless: 24 | 25 | 那我們該怎麼辦呢 :question: 26 | 27 | 有沒有一種東西,可以把它整合,甚至我們執行一個指令,就可以把全部 28 | 29 | 我們依賴的 repo 都做檢查並且更新,如果是這樣的話,整個流程會更安全, 30 | 31 | 也更不會出錯 :relaxed: 32 | 33 | 答案是有的 :thumbsup: 34 | 35 | 那就是我們接下來要介紹的 git Submodule,如果你也有上述的使用情境, 36 | 37 | 那你就更需要學習 git Submodule 了 :flushed: 38 | 39 | 再更簡單一點的說明,你可以把它想成是一個很大的主 repo,然後裡面依賴 40 | 41 | 很多 A repo、B repo、C repo 之類,而這些 A repo,B repo,C repo,你就可以 42 | 43 | 把它想成是 Submodule,這樣是不是更清楚了呢 :grin: 44 | 45 | 既然了解了使用情境,那我們就來看看它該怎麼使用 :satisfied: 46 | 47 | ## how to create submodule 48 | 49 | 手把手帶大家動手做 [Youtube Tutorial PART 1 - git Submodule tutorial - how to create submodule](https://youtu.be/IDMWLJCbCGo), 50 | 51 | 先介紹一下 repo ,這邊有兩個 repo,分別為 main_project repo 以及 a_project repo, 52 | 53 | main_project repo 內容如下, 54 | 55 | ![alt tag](https://i.imgur.com/mLaKJyX.png) 56 | 57 | 接著,我們要建立 a_project 為 Submodule,建立 Submodule 的語法如下, 58 | 59 | ```cmd 60 | git submodule add [] [--] [] 61 | ``` 62 | 63 | 實際範例 64 | 65 | ```cmd 66 | git submodule add git@blue.github.com:blue-rubiks/a_project.git 67 | ``` 68 | 69 | ![alt tag](https://i.imgur.com/bexQ5Op.png) 70 | 71 | 這時候,你如果觀察資料夾,會發現多出一些東西 72 | 73 | ![alt tag](https://i.imgur.com/djYEPu9.png) 74 | 75 | 也可以用 `git status` 觀察, 76 | 77 | ![alt tag](https://i.imgur.com/I9jFYOb.png) 78 | 79 | 主要是多了 `.gitmodules` 以及 `a_project`, 80 | 81 | 可以看一下 `.gitmodules` 的內容,裡面就是 path 和 url 82 | 83 | ![alt tag](https://i.imgur.com/kk8qfTE.png) 84 | 85 | 雖然表面上是兩個改動,但實際上卻還有一個改動, 86 | 87 | 它存在 `main_project/.git/config` 中 88 | 89 | ![alt tag](https://i.imgur.com/mmyFYAk.png) 90 | 91 | 為什麼我要特別說這個,因為假如今天你想要移除 submodule, 92 | 93 | 你就必須到這裡移除和 submodule 相關的內容。 94 | 95 | 接著一些網路上的教學會說需要再執行以下兩個指令, 96 | 97 | ```cmd 98 | git submodule init 99 | git submodule update 100 | ``` 101 | 102 | 但我自己測試似乎是不需要的 ( 雖然執行了也不會有什麼影響 ), 103 | 104 | 所以就先記錄就好,不要理它 :smirk: 105 | 106 | 接著就一般 git 的操作後,push 就完成了 :smiley: 107 | 108 | ![alt tag](https://i.imgur.com/8c5ygMn.png) 109 | 110 | 可以使用 `git submodule status` 查看目前 submodule 的狀態 111 | 112 | ```cmd 113 | git submodule status 114 | ``` 115 | 116 | ![alt tag](https://i.imgur.com/44bW6Bs.png) 117 | 118 | push 之後,可以到 github 網頁上看, 119 | 120 | 會發現在 main_project repo 中的 submodule 有一個小圖示, 121 | 122 | 並且有一個 commit id, 123 | 124 | ![alt tag](https://i.imgur.com/K0Z5tAa.png) 125 | 126 | 而這個 commit id,就是 a_project repo 中的 commit id, 127 | 128 | ![alt tag](https://i.imgur.com/rcyWCGW.png) 129 | 130 | 學習完了如何建立 Submodule,接下來來看看如何更新 Submodule。 131 | 132 | ## how to update submodule 133 | 134 | 手把手帶大家動手做 [Youtube Tutorial PART 2 - git Submodule tutorial - how to update submodule](https://youtu.be/ogZoZOVyAYI), 135 | 136 | 假設 a_project repo 也是我們自己維護的,當我們對它更新時,會發生什麼事情 :question: 137 | 138 | 如果你進入 a_project repo 中,你會發現它其實就是一個我們常見的 git repo, 139 | 140 | 可以正常的 commit 以及 push, 141 | 142 | ![alt tag](https://i.imgur.com/1REbrbK.png) 143 | 144 | **只是你從外面 ( 也就是 main_project repo ) 看到的是像一種參照而已**, 145 | 146 | 上面這句話很重要,因為我們現在更新了 a_project repo ,可是 main_project 並不 147 | 148 | 知道它有任何更新,切回 main_project repo 中,你會發現確實有更動, 149 | 150 | ![alt tag](https://i.imgur.com/jlPgz8P.png) 151 | 152 | 所以這時候就必須要執行一般的 git 操作並且 push, 153 | 154 | ![alt tag](https://i.imgur.com/bM8Ow3O.png) 155 | 156 | 這樣才算是成功,最後這一步驟 **很重要**,有些人會忘記,如果你沒有加上這步驟, 157 | 158 | 你的 main_project repo 就不知道 a_project repo 有做修改,儘管它是參照的概念, 159 | 160 | 還是需要此步驟。 161 | 162 | 剛剛我們是自己改動 a_project repo,現在另一種情境,是別人改動 a_project repo, 163 | 164 | 那這時候,我們如何更新 Submodule 呢 :question: 165 | 166 | 剛剛說過如果我們進去 a_project repo 中,它就像一個正常的 git 一樣, 167 | 168 | 所以依照這個原則, 169 | 170 | 我只要進去 a_project repo 中,並切執行 `git pull` 即可,如下圖, 171 | 172 | ( 為了方便,我直接去 github 網頁端新增一個檔案,模擬有人更新 repo ) 173 | 174 | ![alt tag](https://i.imgur.com/M9noUKg.png) 175 | 176 | 但是,因為現在是只有一個 Submodule ,如果你有 N 個 Submodule 呢 ? 177 | 178 | 這樣我相信會非常麻煩,因為要一個一個更新 :sob: 179 | 180 | 所以,比較好的方法應該是使用以下指令一次更新, 181 | 182 | ``` cmd 183 | git submodule update --remote 184 | ``` 185 | 186 | 更多詳細的 update 可參考 [git-submodule-update](https://git-scm.com/docs/git-submodule#git-submodule-update--init--remote-N--no-fetch--no-recommend-shallow-f--force--checkout--rebase--merge--referenceltrepositorygt--depthltdepthgt--recursive--jobsltngt--ltpathgt82308203), 187 | 188 | `--remote` 的更多說明可以參考 [官方的說明](https://git-scm.com/docs/git-submodule#git-submodule---remote), 189 | 190 | ![alt tag](https://i.imgur.com/m2ICwPY.png) 191 | 192 | 可是這時候你如果切到 a_project repo 你會發現它竟然 **HEAD detached** 了 :confused: 193 | 194 | ![alt tag](https://i.imgur.com/JHMn26S.png) 195 | 196 | 其實這是正常的,因為默認的行為會將它 checkout 到最新的 commit id 上, 197 | 198 | 以這個範例 `HEAD detached at 066d0db` 來說, 199 | 200 | 就是執行了 `git checkout '066d0db'`,所以我們要修正它, 201 | 202 | 修正方法如下圖, 203 | 204 | ![alt tag](https://i.imgur.com/VfwxJW4.png) 205 | 206 | 簡單說就是要建立一個分支 ,然後再回到 master merge 該 ( `066d0db` ) 分支。 207 | 208 | 但這樣步驟真的有點多 :sweat_smile: 209 | 210 | 所以更簡單的方法,可以直接使用以下指令, 211 | 212 | ```cmd 213 | git submodule update --remote --merge 214 | ``` 215 | 216 | `--merge` 的 [官方說明](https://git-scm.com/docs/git-submodule#git-submodule-merge) 如下 217 | 218 | ```text 219 | the commit recorded in the superproject will be merged into the current branch in the submodule. 220 | ``` 221 | 222 | ![alt tag](https://i.imgur.com/0YmQoP4.png) 223 | 224 | 或 225 | 226 | ```cmd 227 | git submodule update --remote --rebase 228 | ``` 229 | 230 | `--rebase` 的 [官方說明](https://git-scm.com/docs/git-submodule#git-submodule-rebase) 如下 231 | 232 | ```text 233 | the current branch of the submodule will be rebased onto the commit recorded in the superproject. 234 | ``` 235 | 236 | ![alt tag](https://i.imgur.com/MHLvL8T.png) 237 | 238 | 這種作法更方便,也解決了 **HEAD detached** 的問題 :thumbsup: 239 | 240 | ## how to clone submodule 241 | 242 | 手把手帶大家動手做 [Youtube Tutorial PART 3 - git Submodule tutorial - how to clone submodule](https://youtu.be/f5_O5Iu6pJo), 243 | 244 | 這邊教大家如何 clone Submodule 的專案,執行一般的 clone 指令, 245 | 246 | ```cmd 247 | git clone git@github.com:blue-rubiks/main_project.git 248 | ``` 249 | 250 | 這時候我們看一下資料夾, 251 | 252 | ![alt tag](https://i.imgur.com/aiTpngo.png) 253 | 254 | 的確有將 a_project repo 一起 clone 下來,但不要高興的太早, 255 | 256 | 因為,你如果進去看這些資料夾查看,你會發現它的內容都是空的 :sob: 257 | 258 | ![alt tag](https://i.imgur.com/xZuY1tm.png) 259 | 260 | 這時候,我們要先 init submodule, 261 | 262 | ```cmd 263 | git submodule init 264 | ``` 265 | 266 | ![alt tag](https://i.imgur.com/bK6IzKW.png) 267 | 268 | 執行之後,它會將 `.gitmodules` 中的 url 寫入 `.git/config` 裡面, 269 | 270 | ![alt tag](https://i.imgur.com/vknhZHR.png) 271 | 272 | 這時候,submodule 都設定好了, 273 | 274 | 只需要再執行 update ( update 會依照 `.git/config` 裡面的設定更新 ), 275 | 276 | 指令如下, 277 | 278 | ```cmd 279 | git submodule update --recursive 280 | ``` 281 | 282 | 以下為 `--recursive` 官方說明 283 | 284 | ```text 285 | If --recursive is specified, this command will recurse into the registered submodules, and update any nested submodules within. 286 | ``` 287 | 288 | ![alt tag](https://i.imgur.com/SJFZW0U.png) 289 | 290 | 成功將 submodule 的內容 clone 下來了 :smile: 291 | 292 | ![alt tag](https://i.imgur.com/G9BXL77.png) 293 | 294 | 上面的兩段指令,也可以合併成一段, 295 | 296 | ```cmd 297 | git submodule update --init --recursive 298 | ``` 299 | 300 | 如果我們一開始就知道我們要 clone 的專案本身就有包含 submodule, 301 | 302 | 可以直接執行以下指令, 303 | 304 | ```cmd 305 | git clone --recurse-submodules git@github.com:blue-rubiks/main_project.git 306 | ``` 307 | 308 | 一次就完成全部的部分,包含 init 以及更新 submodule :satisfied: 309 | 310 | 詳細說明可參考 [git-clone---recurse-submodulesltpathspec](https://git-scm.com/docs/git-clone#git-clone---recurse-submodulesltpathspec), 311 | 312 | 但通常我們應該是不會一開始就知道這個專案有包含 submodule。 313 | 314 | ### how to remove submodule 315 | 316 | 手把手帶大家動手做 [Youtube Tutorial PART 4 - git Submodule tutorial - how to remove submodule](https://youtu.be/imndFN7AvFA), 317 | 318 | 要移除 submodule 的步驟比較多,這邊教大家如何移除, 319 | 320 | ```cmd 321 | git submodule deinit a_project 322 | ``` 323 | 324 | ![alt tag](https://i.imgur.com/YcUK6gU.png) 325 | 326 | 其實 deinit 就是刪除 `.git/config` 裡面的 submodule 相關設定, 327 | 328 | ![alt tag](https://i.imgur.com/Ek30GqW.png) 329 | 330 | 接著打開 `.gitmodules` 移除相關的設定,在這邊因為我們只有一個 submodule, 331 | 332 | 所以直接將 `.gitmodules` 刪除即可。 333 | 334 | ( 如果只要刪除特定的 submodule,就進去刪除相關的部分 ) 335 | 336 | 接著使用 git rm 指令,詳細用法可參考 [git-rm](https://git-scm.com/docs/git-rm), 337 | 338 | ```cmd 339 | git rm --cached a_project 340 | ``` 341 | 342 | ![alt tag](https://i.imgur.com/YVHYuDE.png) 343 | 344 | 如果有加上 `--cached` ,本地端的 submodule repo 會被保留,所以 345 | 346 | 如果沒有要保留,可以不加上 `--cached`,將會把本地端的 submodule repo 刪除。 347 | 348 | 接著再移除 `.git/modules/a_project`, 349 | 350 | ```cmd 351 | rm -rf .git/modules/a_project 352 | ``` 353 | 354 | 接著就 `git add` 以及 `git commit` 355 | 356 | ![alt tag](https://i.imgur.com/LHCpjtg.png) 357 | 358 | 最後在把空的 a_project 資料夾刪除 359 | 360 | ```cmd 361 | rm -rf a_project 362 | ``` 363 | 364 | 步驟雖然多了點,但其實不難 :smirk: 365 | 366 | ### Synchronizes Submodule 367 | 368 | 詳細可參考 [git-submodule-sync](https://git-scm.com/docs/git-submodule#git-submodule-sync--recursive--ltpathgt82308203) 369 | 370 | 以下為官方說明 371 | 372 | ```text 373 | Synchronizes submodules' remote URL configuration setting to the value specified in .gitmodules. 374 | ``` 375 | 376 | 有時候如果 Submodule 的 remote url 有變更的時候,就需要使用此指令更新。 377 | 378 | ## Donation 379 | 380 | 文章都是我自己研究內化後原創,如果有幫助到您,也想鼓勵我的話,歡迎請我喝一杯咖啡 :laughing: 381 | 382 | ![alt tag](https://i.imgur.com/LRct9xa.png) 383 | 384 | [贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8) 385 | 386 | ## License 387 | 388 | MIT license 389 | -------------------------------------------------------------------------------- /git_subtree_turorial.md: -------------------------------------------------------------------------------- 1 | # git subtree tutorial :memo: 2 | 3 | 手把手帶大家動手做 [Youtube Tutorial PART 1 - git subtree tutorial - how to create subtree](https://youtu.be/kEvgK2gH_vg) 4 | 5 | 還記得之前和大家介紹過 [git Submodule tutorial 📝](https://github.com/twtrubiks/Git-Tutorials/blob/master/git_submodule_turorial.md) 這個指令嗎 :question: 6 | 7 | 忘記得趕快去複習一下吧 :smirk: 8 | 9 | 今天我要再來介紹一個類似功能的指令,名稱叫做 **git-subtree**,如果你用 git-subtree 和 git submodule 10 | 11 | 去 google,你會發現蠻多人拿這兩個來比較的,然後都說什麼 git-subtree 比較推薦之類的,但我這邊先 12 | 13 | 不下結論,結論在最後面,我先帶大家來了解他怎麼使用。 14 | 15 | ## 簡介 16 | 17 | 我先用兩句話來描述 git submodule 和 git subtree 的差異, 18 | 19 | 20 | ***git submodule 是 link 的概念,而 git subtree 則是 copy的概念*** 21 | 22 | ( 如果你看不懂,建議先去閱讀之前我介紹的 Submodule 的 [文章](https://github.com/twtrubiks/Git-Tutorials/blob/master/git_submodule_turorial.md) 吧 ) 23 | 24 | 25 | git subtree 的指令沒有很多,主要指令如下, 26 | 27 | ```text 28 | 'git subtree' add -P 29 | 'git subtree' pull -P 30 | 'git subtree' push -P 31 | 'git subtree' merge -P 32 | 'git subtree' split -P [OPTIONS] [] 33 | ``` 34 | 35 | 更多詳細內容可參考 [git-subtree](https://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt) 文件。 36 | 37 | ## 教學 38 | 39 | ### how to create git subtree 40 | 41 | 先介紹一下 repo ,這邊有兩個 repo,分別為 main_project_subtree 42 | repo 以及 a_project_subtree repo, 43 | 44 | 先把 main_project_subtree repo clone 下來, 45 | 46 | ```cmd 47 | git clone git@github.com:blue-rubiks/main_project_subtree.git 48 | ``` 49 | 50 | main_project_subtree repo log 如下, 51 | 52 | ![alt tag](https://i.imgur.com/I6i93rr.png) 53 | 54 | 55 | 接下來,要加入 subtree ( 也就是 a_project_subtree ),使用以下指令 56 | 57 | ```cmd 58 | git subtree add --prefix=a_project_subtree --squash git@github.com:blue-rubiks/a_project_subtree.git master 59 | ``` 60 | 61 | or 62 | 63 | ```cmd 64 | git subtree add -P a_project_subtree --squash git@github.com:blue-rubiks/a_project_subtree.git master 65 | ``` 66 | 67 | `--prefix` option [文件](https://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt) 說明如下, 68 | 69 | ```text 70 | -P :: 71 | --prefix=:: 72 | Specify the path in the repository to the subtree you 73 | want to manipulate. This option is mandatory 74 | for all commands. 75 | ``` 76 | 77 | 簡單說,prefix 就是指定一個目錄。 78 | 79 | `--squash` option [文件](https://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt) 說明如下, 80 | 81 | ```text 82 | With '--squash', imports only a single commit from the subproject, rather than its entire history. 83 | ``` 84 | 85 | 也就是說,如果沒有使用 `--squash`,會將 a_project_subtree 的 log ( 歷史訊息 ) 全部顯示出來 86 | 87 | ( 請有 log 會變很長又有點亂的心理準備 :scream: ), 88 | 89 | 但通常我們不太需要顯示全部的 log ( 尤其是依賴的 repo ),所以記得加上 `--squash` option, 90 | 91 | 這樣的話,就會將全部的 log 合成一個 log 92 | 93 | ( 其實是兩個,因為還會有一個 merge 的 log :smiley: ) 94 | 95 | ![alt tag](https://i.imgur.com/aUSe38E.png) 96 | 97 | main_project_subtree repo log 會變成如下,會多兩個 log, 98 | 99 | ![alt tag](https://i.imgur.com/NS0aB4B.png) 100 | 101 | 如果你覺得以上指令實在太長了,可以拆成兩步驟,先執行, 102 | 103 | ```cmd 104 | git remote add a_project_subtree git@github.com:blue-rubiks/a_project_subtree.git 105 | ``` 106 | 107 | 更多詳細資料可參考 [git-remote](https://git-scm.com/docs/git-remote)。 108 | 109 | 然後執行 110 | 111 | ```cmd 112 | git subtree add -P a_project_subtree --squash a_project_subtree master 113 | ``` 114 | 115 | 其實就是將 remote url 變成 a_project_subtree 而已 :thumbsup: 116 | 117 | 118 | ### how to push git subtree 119 | 120 | [Youtube Tutorial PART 2 - git subtree tutorial - how to push subtree](https://youtu.be/Df3zc1VOqN8) 121 | 122 | 現在的 main_project_subtree 基本上已經將 a_project_subtree 整個 copy 過來了,就算現在 123 | 124 | a_project_subtree remote repo 被刪除也沒關係 ( 因為是 copy 過來的 ),而且 developer 甚至 125 | 126 | 不需要知道有 a_project_subtree 的存在,直接使用一般的 git 操作即可,也就是說,現在 127 | 128 | a_project_subtree 就像是在 main_project_subtree 裡面的一個目錄一樣。 129 | 130 | 如果你修改了 a_project_subtree ,並且執行 git push,在 main_project_subtree 中可以看到 131 | 132 | 資料被修改,但是在 a_project_subtree 中會看不到修改,因為還沒有 push 到 a_project_subtree, 133 | 134 | 那該如何 push 到 a_project_subtree 上呢 :question: 135 | 136 | 可以使用以下指令 137 | 138 | ```cmd 139 | git subtree push -P a_project_subtree a_project_subtree master 140 | ``` 141 | 142 | 或是 143 | 144 | ```cmd 145 | git subtree push -P a_project_subtree git@github.com:blue-rubiks/a_project_subtree.git master 146 | ``` 147 | 148 | update a_project_subtree, 149 | 150 | ![alt tag](https://i.imgur.com/QsyZEcK.png) 151 | 152 | 接著 push 改變到 a_project_subtree repo, 153 | 154 | ![alt tag](https://i.imgur.com/F8SJ2pn.png) 155 | 156 | 注意 `4/4 (2)` 這個,他每次都會重新 ( 重源頭 ) 計算,所以如果你一直增加 a_project_subtree 的 commit, 157 | 158 | 每次 push 到 a_project_subtree 的速度會越來越慢,該如何解決 :question: 159 | 160 | 後面會介紹 :smirk: 161 | 162 | ![alt tag](https://i.imgur.com/PnpAmqU.png) 163 | 164 | 如果你要 push 改變到 main_project_subtree,直接執行一般的 `git push` 操作就行了, 165 | 166 | ![alt tag](https://i.imgur.com/2T9Bn13.png) 167 | 168 | 到這邊我們休息一下 :relaxed: 169 | 170 | 在簡介的時候我們說過 **git submodule 是 link 的概念,而 git subtree 則是 copy 的概念**,所以如果你 171 | 172 | 仔細看 main_project_subtree 以及 a_project_subtree 的 log,你會發現他們是不一樣的 ( 因為 git subtree 173 | 174 | 有自己的策略 ),而之前介紹的 git submodule 則會有相同的 commit id 以及 submodule 的 commit link。 175 | 176 | ### how to pull git subtree 177 | 178 | [Youtube Tutorial PART 3 - git subtree tutorial - how to pull/create subtree](https://youtu.be/dE-D2yrD4ws) 179 | 180 | 如果今天我們要更新 a_project_subtree repo 可以使用以下指令, 181 | 182 | ```cmd 183 | git subtree pull -P a_project_subtree a_project_subtree master 184 | ``` 185 | 186 | 或是 187 | 188 | ```cmd 189 | git subtree pull -P a_project_subtree git@github.com:blue-rubiks/a_project_subtree.git master 190 | ``` 191 | 192 | 執行後,應該會跳出 `fatal: refusing to merge unrelated histories` 失敗的訊息, 193 | 194 | ![alt tag](https://i.imgur.com/hcqBtrK.png) 195 | 196 | 會出現這個的原因是因為我們在前面有執行 `git subtree add -P a_project_subtree --squash ` 197 | 198 | 中的 `--squash`,所以 pull 的時候也必須加上,也就是如下, 199 | 200 | ![alt tag](https://i.imgur.com/AoOcXFa.png) 201 | 202 | 這樣才可以成功 pull :smile: 203 | 204 | ### git subtree split 205 | 206 | split 這個指令比較特別,在這邊我提一下,在前面的 [how to push git subtree](https://github.com/twtrubiks/Git-Tutorials/blob/master/git_subtree_turorial.md#how-to-push-git-subtree) 部分有提到 `4/4 (2)` 207 | 208 | 這個是他每次都會重新計算,所以如果你一直增加 commit,push 速度會越來越慢的這個問題, 209 | 210 | 而 split 可以解決這個問題,他會從一個最新的點開始計算,不用每次都重頭計算 :smile: 211 | 212 | 先來看一下 [文件](https://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt),`--rejoin` option 213 | 214 | ```text 215 | If you do all your merges with '--squash', don't use 216 | '--rejoin' when you split, because you don't want the 217 | subproject's history to be part of your project anyway. 218 | ``` 219 | 220 | 我們在 `git subtree add` 以及 `git subtree pull` 都有使用 `--squash`,所以 221 | 222 | 如果再使用`--rejoin` option 的話,會導致 a_project_subtree 的 history log 都 223 | 224 | 被 clone 回來,所以我們必須再加上 `--ignore-joins option`,指令如下, 225 | 226 | ```cmd 227 | git subtree split --rejoin --prefix=a_project_subtree --ignore-joins 228 | ``` 229 | 230 | ![alt tag](https://i.imgur.com/owS1Nz7.png) 231 | 232 | 這樣就完成 split 了,當我們如果有改動 a_project_subtree,`4/4 (2)` 這個就會重最新的點開始計算, 233 | 234 | 不會每次都重頭計算,導致 push 速度越來越慢 :satisfied: 235 | 236 | 237 | ## 結論 238 | 239 | 介紹完了 subtree ,一定要來說說我對 subtree 以及 submodule 的看法 :laughing: 240 | 241 | 在 submodule 中執行 init 以及如果要移除掉 submodule 時,步驟都比較繁瑣, 242 | 243 | 相對 subtree 來說,subtree 簡單很多,因為對 developer 來說,他就像一個 244 | 245 | 目錄一樣,就算今天 subtree 的 remote repo 被刪除也不擔心 :smiley: 246 | 247 | 但是我相信大家一定也發現了,使用 subtree 的時候,我們如果要切換 branch 248 | 249 | 並沒有想像中的容易 ( 在 submodule 中,我們可以進到依賴的 repo 中,然後 250 | 251 | 像操作一般的 git 使用 `git checkout` 來切換 branch,但在 subtree 中卻無法 252 | 253 | 這樣使用,因為他是整個 copy 過來的,不像 submodule 那樣是 link 的概念。 ) 254 | 255 | 所以如果有切分支 ( 你依賴的 repo ),建議還是使用 submodule 會比較方便, 256 | 257 | 另外一個點是,我覺得使用 subtree 時,會讓 log 變得有點亂 :weary:,不像 258 | 259 | submodule 那樣清楚很多 :smile: 260 | 261 | 而且 subtree 的侵略性比較強 ( commit id 都會改變),大家在使用時,要多了解 262 | 263 | 一下,雖然 subtree 指令看似只有幾個,但是整體使用下來,還是很多細節要注意 :sweat_smile: 264 | 265 | 266 | ## Donation 267 | 268 | 文章都是我自己研究內化後原創,如果有幫助到您,也想鼓勵我的話,歡迎請我喝一杯咖啡 :laughing: 269 | 270 | ![alt tag](https://i.imgur.com/LRct9xa.png) 271 | 272 | [贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8) 273 | 274 | ## License 275 | 276 | MIT license 277 | -------------------------------------------------------------------------------- /pr-tutorial/README.md: -------------------------------------------------------------------------------- 1 | # PR (Pull Request) 教學 2 | 3 | 先來說說甚麼時候可以使用 PR (Pull Request), 4 | 5 | * 情境一 6 | 7 | 你在網路上看到一個很棒的專案, 然後你很想要為這個專案盡一份心力, 這時候就你就可以透過 PR 這個機制, 8 | 9 | 通常你不會有這個專案的權限, 所以一定是 Fork 這個專案到自己的 repo 下修改 -> 修改完後推回自己的分支 10 | 11 | -> 再對對應的原專案發 PR(注意對方的以及自己的 branch ) -> 等作者測試覺得沒問題後合併進主分支 12 | 13 | -> 恭喜你對社群盡了一分心力 :smile: 14 | 15 | * 情境二 16 | 17 | 公司也可以使用 PR 的模式進行多人開發, 但通常在這個情況下你都會有專案的權限(和情境一不同), 所以你就 18 | 19 | 不需要 Fork, 直接開 branch 後對特定的 branch(可能是 develop) 發 PR 即可. 20 | 21 | 22 | **情境一和情境二基本上都是一樣的概念, 唯一差別就是是否有 repo 的權限, 如果沒有就需要 Fork.** 23 | 24 | 今天主要會透過 情境二 和大家簡單介紹 PR 的操作. 25 | 26 | * [Youtube Tutorial - github PR (Pull Request) 教學](https://youtu.be/bXOdD-bKfkA) - [文章快速連結](https://github.com/twtrubiks/Git-Tutorials/tree/master/pr-tutorial#github-pr-pull-request-%E6%95%99%E5%AD%B8) 27 | 28 | * [Youtube Tutorial - github CLI PR 教學](https://youtu.be/AD8X11lq3gQ) - [文章快速連結](https://github.com/twtrubiks/Git-Tutorials/tree/master/pr-tutorial#github-cli-pr-%E6%95%99%E5%AD%B8) 29 | 30 | ## github PR (Pull Request) 教學 31 | 32 | 使用 [Git-Flow 基本教學](https://github.com/twtrubiks/Git-Tutorials/tree/master/Git-Flow) 的概念來做教學, 33 | 34 | 為了簡單一點, 在這邊就只有 main(master), develop, feature 分支. 35 | 36 | 今天有個需求來了, 我首先先從 develop 開一個 feature 分支, 37 | 38 | 假設做完了需求, 就把 feature 分支 push 上去, 然後開始發 PR. 39 | 40 | 現在要對 develop 發 PR, 要發的分支是 feature. (如下圖) 41 | 42 | ![alt tag](https://i.imgur.com/XfTq0hc.png) 43 | 44 | 選好之後就可以按下 Create pull request, 45 | 46 | 接著你會看到專案下有一個 PR, 47 | 48 | ![alt tag](https://i.imgur.com/ad8BM6T.png) 49 | 50 | 這時候可能是技術主管(依照各公司的流程)看到這個 PR, 如果沒有甚麼問題, 51 | 52 | 可以選擇要使用哪種的 Merge pull request 的方式. 53 | 54 | ![alt tag](https://i.imgur.com/JX9pQDU.png) 55 | 56 | 有三種 Merge pull request 可以選擇, 57 | 58 | * Create a Merge Commit 59 | 60 | 基本上這個就是一般的 merge, 但我通常不喜歡這個, 因為會有無意義的 merge commit. 61 | 62 | * Squash and Merge 63 | 64 | 這個就是假如你有一個功能, 總共使用了 10 個 commit 才完成這個功能, 但是這些 commit 65 | 66 | 其實都沒有太重要的資訊, 這時候你可以選擇使用 Squash 的方式, 把這 10 個 commit 合併成 67 | 68 | 一個 commit. (其他的 commit 會變成你的 message). 69 | 70 | * Rebase and Merge 71 | 72 | 這個和 Create a Merge Commit 的差別就是他不會有無意義的 merge commit, 他有移花接木 73 | 74 | 的概念, 你的分支上有 3個 commit, 使用這方法合併後就是會多出 3個 commit. 75 | 76 | 至於要使用哪一種, 就看各公司的需求了 :smile: 77 | 78 | 決定好 Merge pull request 的方式後就可以直接按下了, 你會發現他顯示 Merged, 79 | 80 | 並且也可選擇是否刪除該分支以及 revert 這個 PR. 81 | 82 | ![alt tag](https://i.imgur.com/ZuJ2eh1.png) 83 | 84 | 基本上整個流程就是這樣, develop 向 main 發送 PR 也是一樣的概念. 85 | 86 | ## github CLI PR 教學 87 | 88 | 如果你有看前面的文章, 你就會覺得很麻煩 :expressionless: 89 | 90 | 因為每次都要手動去網頁上點, 工程師肯定要使用 CLI, 91 | 92 | 今天我就來教大家這個 [github CLI](https://cli.github.com/) :laughing: 93 | 94 | 連 github 的網頁都不需要開 :satisfied: 95 | 96 | 首先是安裝方法 97 | 98 | [https://github.com/cli/cli/blob/trunk/docs/install_linux.md](https://github.com/cli/cli/blob/trunk/docs/install_linux.md) 99 | 100 | 接著是登入的方法 [gh_auth_login](https://cli.github.com/manual/gh_auth_login) 101 | 102 | ```cmd 103 | gh auth login 104 | ``` 105 | 106 | 選擇你要的登入方式即可. 107 | 108 | 順便一提, github CLI 的文件寫的很好, 大家可以參考他的文件玩玩看 [GitHub CLI document](https://cli.github.com/manual/). 109 | 110 | 接下來, 就要來體驗 github CLI 的強大了 :satisfied: 111 | 112 | 現在有一個 `feature_cli` 分支, 當 push 分支之後, 113 | 114 | (溫馨提醒 :exclamation: :exclamation: 要發一個 PR, 這個 branch 一定要 push 到 remote) 115 | 116 | (也就是說, 你沒辦法去發送一個 PR 只存在 local 端但卻不存在 remote 端) 117 | 118 | (如果你只想要 PR 特定的 commit, 請使用 `git checkout -b [branch] [commit_id]` 再發送 PR) 119 | 120 | 就可以透過 github CLI 發 PR, 要向 develop 分支發 PR, 指令如下 121 | 122 | 文件可參考 [https://cli.github.com/manual/gh_pr_create](https://cli.github.com/manual/gh_pr_create) 123 | 124 | ```cmd 125 | gh pr create --base develop --head feature_cli 126 | ``` 127 | 128 | 如果你目前就在 `feature_cli` 分支底下, 也可以只輸入 129 | 130 | ```cmd 131 | gh pr create --base develop 132 | ``` 133 | 134 | 成功透過 github CLI 發送 PR 135 | 136 | ![alt tag](https://i.imgur.com/sZF3SuH.png) 137 | 138 | github 上面也確實有這個 PR 139 | 140 | ![alt tag](https://i.imgur.com/7atBIzY.png) 141 | 142 | 也可以搭配其中的指令 143 | 144 | ```cmd 145 | gh pr create --base dev -a @me -l bug -r twtrubiks 146 | ``` 147 | 148 | `-a` 代表指定這個 pr 給自己 149 | 150 | `-l` label 設定為 bug 151 | 152 | `-r` 設定 reviews 為 twtrubiks 153 | 154 | 除了發 PR 之外, 也可以透過 github CLI 接受 PR, 指令如下 155 | 156 | 文件可參考 [https://cli.github.com/manual/gh_pr_merge](https://cli.github.com/manual/gh_pr_merge) 157 | 158 | ```cmd 159 | gh pr merge -s 2 160 | ``` 161 | 162 | 參數說明, 163 | 164 | `-m` Create a Merge Commit. 165 | 166 | `-s` Squash and Merge. 167 | 168 | `-r` Rebase and Merge. 169 | 170 | 數字 2 則代表要合併的 PR 編號. 171 | 172 | ![alt tag](https://i.imgur.com/hedvtIi.png) 173 | 174 | github 上面確實已經合併這個 PR 175 | 176 | ![alt tag](https://i.imgur.com/h6akTEd.png) 177 | 178 | 也可以建立 issue [gh_issue_create](https://cli.github.com/manual/gh_issue_create) 179 | 180 | ```cmd 181 | gh issue create -a @me -l bug 182 | ``` 183 | 184 | `-a` 代表指定這個 issue 給自己 185 | 186 | `-l` label 設定為 bug 187 | 188 | 還有非常多的指令, 就不一一介紹給大家了, 大家請自行研究 :relaxed: 189 | 190 | 基本上, github CLI 功能是非常強大的 :smile: 191 | 192 | ### 其他 193 | 194 | gh 預設的 editor 是 nano, 以下指令為修改成 vim 195 | 196 | ```cmd 197 | gh config set editor vim 198 | ``` 199 | --------------------------------------------------------------------------------