{"id":20619722,"url":"https://github.com/twtrubiks/git-tutorials","last_synced_at":"2025-05-15T00:07:56.374Z","repository":{"id":37561583,"uuid":"63858639","full_name":"twtrubiks/Git-Tutorials","owner":"twtrubiks","description":"Git-Tutorials  GIT基本使用教學 :memo:","archived":false,"fork":false,"pushed_at":"2025-04-23T10:56:58.000Z","size":109,"stargazers_count":1013,"open_issues_count":2,"forks_count":307,"subscribers_count":45,"default_branch":"master","last_synced_at":"2025-04-23T11:38:58.912Z","etag":null,"topics":["git-flow","git-rebase","github","pr","pull-request","ssh-key","tutorial"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twtrubiks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-07-21T10:04:47.000Z","updated_at":"2025-04-23T10:57:02.000Z","dependencies_parsed_at":"2024-01-14T02:41:10.633Z","dependency_job_id":"948436d8-1b05-4f79-abc0-44ecc928fcf8","html_url":"https://github.com/twtrubiks/Git-Tutorials","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2FGit-Tutorials","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2FGit-Tutorials/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2FGit-Tutorials/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2FGit-Tutorials/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twtrubiks","download_url":"https://codeload.github.com/twtrubiks/Git-Tutorials/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254249198,"owners_count":22039029,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["git-flow","git-rebase","github","pr","pull-request","ssh-key","tutorial"],"created_at":"2024-11-16T12:12:21.941Z","updated_at":"2025-05-15T00:07:51.337Z","avatar_url":"https://github.com/twtrubiks.png","language":null,"readme":"# Git-Tutorials 基本使用教學  :memo:\r\n\r\n因為小弟覺得這東西蠻有趣的，所以就簡單寫個教學文，順便記錄一下 :memo:，希望能幫助想學的人 :smile:\r\n\r\n如果教學有誤再請糾正 :sweat_smile:\r\n\r\n基本使用指令以及安裝可參考小弟之前拍的影片\r\n\r\n* [Youtube Tutorial - github基本教學 - 從無到有](https://www.youtube.com/watch?v=py3n6gF5Y00)\r\n\r\n影片教學包含如何產生 **SSH key**\r\n\r\n如果步驟正確且沒出錯誤，可以在路徑下找到 **.ssh資料夾**，裡面有 **id_rsa** 以及 **id_rsa.pub** 兩個檔案，\r\n\r\n這兩個就是 SSH Key， **id_rsa是私鑰** ，不能洩露出去， **id_rsa.pub是公鑰** ，可以很放心的告訴任何人。\r\n\r\n安裝完 Git 之後，要做的第一件事情就是去設定自己的名字和信箱\r\n\r\n```cmd\r\ngit config --global user.name \"twtrubiks\"\r\ngit config --global user.email \"twtrubiks@gmail.com\"\r\n```\r\n\r\n可以輸入以下來確認是否輸入成功\r\n\r\n```cmd\r\ngit config --global user.name\r\ngit config --global user.email\r\n```\r\n\r\n![alt tag](https://i.imgur.com/5mpS7Ij.jpg)\r\n\r\nGit 設定資料查看，可執行以下指令 ( 文章末會有較詳細的教學 )：\r\n\r\n```cmd\r\ngit config --list\r\n```\r\n\r\n## git init 指令\r\n\r\n初始化 git\r\n\r\n```cmd\r\ngit init\r\n```\r\n\r\n也可以指定資料夾\r\n\r\n```cmd\r\ngit init \u003cdirectory\u003e\r\n```\r\n\r\n## git clone 指令\r\n\r\n複製如圖位置網址 ( 不要複製我的哦~ 複製你自己的 )\r\n![alt tag](https://i.imgur.com/EJ5JNjt.jpg)\r\n\r\ngit clone ( 複製的網址 ) SSH / HTTPS\r\n\r\n( 如果你要使用 https 的方式, 請接著看 [Personal Access Tokens](https://github.com/twtrubiks/Git-Tutorials#personal-access-tokens) )\r\n\r\n```cmd\r\ngit clone git@github.com:twtrubiks/test.git\r\n```\r\n\r\n第一次會出現 SSH 警告，選 YES 即可。\r\n\r\n如圖 ( 下載成功 )，在你的下載路徑下就會多出一個資料夾\r\n\r\n![alt tag](https://i.imgur.com/iIkTlqf.jpg)\r\n\r\n## Personal Access Tokens\r\n\r\n* [Youtube Tutorial - GitHub 教學 - Personal Access Tokens](https://youtu.be/aJRRVCB85k8)\r\n\r\n從 2021/8/13 開始, 如果你用 https 的方式你會發現\r\n\r\n![alt tag](https://i.imgur.com/6YIJSaj.png)\r\n\r\n```text\r\nremote: Support for password authentication was removed on August 13, 2021.\r\nremote: 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.\r\nfatal: Authentication failed for 'https://github.com/xxxxx.git/'\r\n```\r\n\r\n這時候如果我們不想加入 ssh key, 也不想透過加入共同協做的方式,\r\n\r\n可以透過這個 Personal Access Tokens (你可以把他想成臨時的權限),\r\n\r\n先到你的 github 裡的 Settings -\u003e Developer settings,\r\n\r\n選 Personal Access Tokens, 產生你的 token\r\n\r\n![alt tag](https://i.imgur.com/zPVlOjf.png)\r\n\r\n時間這邊你可以自己定義多久後會過期,\r\n\r\n下面的部份則是這個 token 有哪些權限,\r\n\r\n![alt tag](https://i.imgur.com/NNJcYRM.png)\r\n\r\n設定完之後, 就可以複製你的 token\r\n\r\n![alt tag](https://i.imgur.com/q4htIBn.png)\r\n\r\n再回去用 https clone 的方式,\r\n\r\n原本是使用, 帳號 + password (已經不能使用了),\r\n\r\n現在改成, 帳號 + 剛剛的 token 就可以順利 clone 了.\r\n\r\n### 如何改善(加速)大型 repo git clone 速度\r\n\r\n* [Youtube Tutorial - 如何改善(加速)大型 repo git clone 速度](https://youtu.be/YHX0qkQa1UI)\r\n\r\n有時候我們會需要 clone 很大的 repo，執行 `git clone` 都需要很長的時間，是不是有方法可以\r\n\r\n加速 clone 的速度呢 :question:\r\n\r\n直接開始動手嘗試 ( 使用 [django](https://github.com/django/django) 當範例 )，\r\n\r\n`git clone git@github.com:django/django.git`\r\n\r\n( 你會發現 clone 需要一些時間 :triumph:)\r\n\r\n![alt tag](https://i.imgur.com/yMH6L8F.png)\r\n\r\n接著查看 log，`git log`\r\n\r\n![alt tag](https://i.imgur.com/vJkFTr2.png)\r\n\r\n嘗試切換 branch `git checkout stable/2.2.x`\r\n\r\n![alt tag](https://i.imgur.com/UtxJ2ER.png)\r\n\r\n開始改善(加速) clone 的時間，\r\n\r\n可以透過 `--depth` 這個參數來完成，簡單說明一下他的功能，當我們一般執行 clone 之後，\r\n\r\n接著執行 `git log` 你會發現有大量的 log，在某修情況下，你可能不需要那麼多的 log，\r\n\r\n也就是說你可能只需要最近 10 筆的 history commit，甚至你只需要 1 筆 ( 也就是根本不需要\r\n\r\nhistory commit )，這時候就很適合使用 `--depth`。\r\n\r\n`git clone git@github.com:django/django.git --depth 1`\r\n\r\n( 你會發現這次快很多了 )\r\n\r\n![alt tag](https://i.imgur.com/yvkZUZI.png)\r\n\r\n接著查看 log，`git log`\r\n\r\n( 會變快的原因是因為我們只保留最新的一筆 history commit ，\r\n\r\n如果你需要最近 10 筆，改成 --depth 10 即可 )\r\n\r\n![alt tag](https://i.imgur.com/at9Zzq3.png)\r\n\r\n但是會有一個問題，當嘗試切換 branch `git checkout stable/2.2.x`\r\n\r\n( 你會發現你無法切換 remote branch :scream:\r\n\r\n原因是因為使用 `--depth` 相當於是 `--single-branch`，\r\n\r\n所以當然沒有其他的 branch。 )\r\n\r\n![alt tag](https://i.imgur.com/gDaeq1W.png)\r\n\r\n也就是說以下兩條指令其實是相等的\r\n\r\n```cmd\r\ngit clone git@github.com:django/django.git --depth 1\r\ngit clone git@github.com:django/django.git --depth 1 --single-branch\r\n```\r\n\r\n為了解決這個問題，比較好的做好應該是這樣\r\n\r\n```cmd\r\ngit clone git@github.com:django/django.git --depth 1 --no-single-branch\r\n```\r\n\r\n( 這個和 `--single-branch` 比會稍微久一點點，因為每個 branch 的最新一個 history commit 都要 clone 下來 )\r\n\r\n這樣的話，就可以保留 remote 的 branch 了，\r\n\r\n![alt tag](https://i.imgur.com/BkLKVZz.png)\r\n\r\n成功切換 remote 的 branch， `git checkout stable/2.2.x`。\r\n\r\n![alt tag](https://i.imgur.com/VCvcSTr.png)\r\n\r\n最後稍微整理，\r\n\r\n如要 clone 最近一次的 history，而且也需要其他 branch，使用如下，\r\n\r\n`git clone git@github.com:django/django.git --depth 1 --no-single-branch`\r\n\r\n如果你想要指定分支, 加上 `-b`,\r\n\r\n`git clone git@github.com:django/django.git --depth 1 --no-single-branch -b stable/3.1.x`\r\n\r\n如要 clone 最近一次的 history，而且**不需要**其他 branch，使用如下，\r\n\r\n`git clone git@github.com:django/django.git --depth 1 --single-branch`\r\n\r\nor\r\n\r\n`git clone git@github.com:django/django.git --depth 1`\r\n\r\n更多詳細參數說明請參考 [git clone](https://git-scm.com/docs/git-clone)\r\n\r\n## git status 指令\r\n\r\n```cmd\r\ngit status\r\n```\r\n\r\n可以讓我們觀看目前的 repository ( repo 容器 )。\r\n\r\n![alt tag](https://i.imgur.com/5Gt98Vh.jpg)\r\n\r\n意思是目前你的工作區是乾淨的。\r\n\r\n## 工作區與暫存區 ( Stage )\r\n\r\ngit add 意思是把要送出的文件放到暫存區 ( Stage ) ，\r\n\r\n然後執行\r\n\r\ngit commit 就可以把暫存區 ( Stage ) 裡所有修改的內容送到目前的分支上。\r\n\r\n一旦送出 ( git commit ) 後，如果你又沒有對工作區做任何修改，那麼工作區就是\"乾淨\"的。\r\n\r\ngit commit -m \"xxxxx\" 指令，-m 後面輸入的內容是本次修改 ( 送出 ) 的說明，\r\n\r\n盡量輸入一眼就可以看出這次送出修改了什麼的內容\r\n( 方便以後回去觀看能快速了解此次 commit 修改了什麼 )。\r\n\r\n以下 demo 為在一個資料夾內新增一個 Hello.py 檔案\r\n\r\n然後使用 git status 觀看目前的 repository ( repo 容器 )，你會看到 Hello.py 未被追蹤，如下圖\r\n\r\n![alt tag](https://i.imgur.com/dvj1DQh.jpg)\r\n\r\n可以使用如下指令\r\n\r\n```cmd\r\ngit add Hello.py\r\n```\r\n\r\n額外補充，下面這個指令很有趣，大家可以玩玩看\r\n\r\n```cmd\r\ngit add -p\r\n```\r\n\r\n接著再使用\r\n\r\ngit commit -m \"文字\"\r\n\r\n```cmd\r\ngit commit -m \"add Hello.py\"\r\n```\r\n\r\n再使用 git status，你會發現工作區變乾淨了。如下圖\r\n\r\n![alt tag](https://i.imgur.com/6VrieNb.jpg)\r\n\r\n補充，如果只有輸入\r\n\r\n```cmd\r\ngit commit\r\n```\r\n\r\n![alt tag](https://i.imgur.com/yZxKGTU.jpg)\r\n\r\n這時會跳出編輯視窗\r\n\r\n![alt tag](https://i.imgur.com/htNQ0dJ.jpg)\r\n\r\n這時可以按鍵盤的 **Ins鍵** ( 或按鍵盤上的 **英文字 i** ) 即可輸入文字\r\n\r\n![alt tag](https://i.imgur.com/NFy16dp.jpg)\r\n\r\n輸入完先按 **Esc鍵** ，按完後底下的 INSERT 會消失，接著直接打 **:wq** ，再按 enter 就會儲存並離開了。\r\n\r\n更多參數可參考 [https://git-scm.com/docs/git-commit](https://git-scm.com/docs/git-commit) 說明。\r\n\r\n**如何修改最後一次的commit呢 ?**\r\n\r\n有時候我們 commit 完之後，才發現自己的 commit 內容手殘打錯了\r\n\r\n這時候可以使用如下指令，他會跳出編輯視窗給你編輯你上一次的 commit 內容。\r\n\r\n```cmd\r\ngit commit --amend\r\n```\r\n\r\n又或是我們 commit 完之後，才發現自己漏了幾個檔案沒有 add 進去\r\n\r\n這時候可以使用如下指令\r\n\r\n```cmd\r\ngit commit -m \"init commit\"\r\ngit add missing_file.py\r\ngit commit --amend\r\n```\r\n\r\n如上狀況為當我 git commit -m \"init commit\" 之後，\r\n\r\n我發現我漏掉了 **missing_file.py** 這個檔案 ( commit 前忘記 add 進去 ) ，\r\n\r\n這時候就可以使用 git commit --amend 來修改最後一次的 commit 。\r\n\r\n有時候我們會為了方便，直接使用下面的指令一次加入全部的檔案\r\n\r\n```cmd\r\ngit add .\r\n```\r\n\r\n但是加完後發現其實有些檔案是不需要 add 進入的，這時候就可以使用如下指令去取消 add\r\n\r\n```cmd\r\ngit reset HEAD \u003cfile\u003e\r\n```\r\n\r\n範例，路徑下有 A.py 以及 B.py 這兩個檔案，然後我使用 **git add .** 加入，\r\n![alt tag](https://i.imgur.com/0S7TcEB.jpg)\r\n\r\n但加入完我發現其實 B.py 我還沒有要 add 進入，所以我這時候就可以使用 **git reset HEAD B.py** 去還原。\r\n\r\n![alt tag](https://i.imgur.com/3iAyEEx.jpg)\r\n\r\n## git push 指令\r\n\r\n```cmd\r\ngit push\r\n```\r\n\r\n將程式 push 到 github ( or bitbucket 之類 )上 , 如下圖\r\n\r\n![alt tag](https://i.imgur.com/d61Pau6.jpg)\r\n\r\n## 版本控制 - 歷史記錄\r\n\r\n```cmd\r\ngit log\r\n```\r\n\r\n按 **小寫q** 可退出\r\n\r\n![alt tag](https://i.imgur.com/j11afCP.jpg)\r\n\r\n如果覺得版面太雜，可以使用下列指令\r\n\r\n```cmd\r\ngit log --pretty=oneline\r\n```\r\n\r\n按 **小寫q** 可退出\r\n\r\n![alt tag](https://i.imgur.com/jz2cwUA.jpg)\r\n\r\n如果你想要只看某個檔案或某個資料夾的變動, 可以這樣使用, 範例如下\r\n\r\n```cmd\r\ngit log -- folder\r\n```\r\n\r\n```cmd\r\ngit log -- folder/demo.py\r\n```\r\n\r\n另外底下也是一個看 log 的方式（ 很酷 :satisfied:），有 GUI 的感覺（ 來源為文章最後的連結 ）\r\n\r\n```cmd\r\ngit log --graph --pretty=format:\"%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)\u003c%an\u003e%Creset\" --abbrev-commit --date=relative\r\n```\r\n\r\n![alt tag](https://i.imgur.com/XNQisuf.png)\r\n\r\nGit 中，使用 HEAD 表示目前的版本，\r\n\r\n```cmd\r\ngit reset --hard HEAD\r\n```\r\n\r\n![alt tag](https://i.imgur.com/pkFO8pk.jpg)\r\n\r\n如果現在要把目前版本退回到上一個版本，就可以使用 git reset 指令：\r\n\r\n上一個版本就是HEAD~1，\r\n\r\n```cmd\r\ngit reset --hard HEAD~1\r\n```\r\n\r\n![alt tag](https://i.imgur.com/ZThoaUT.jpg)\r\n\r\n上上一個版本就是HEAD~2，\r\n\r\n如果要指定回到某個特定版本：\r\n\r\n![alt tag](https://i.imgur.com/KrCOC71.jpg)\r\n\r\n```cmd\r\ngit reset --hard ad41df36b7\r\n```\r\n\r\n`--hard` 這個參數，有三種選擇，分別為 `--mixed`( default ）`--hard` `--soft`，\r\n\r\n`--hard` 這個參數簡單解釋就是將之前的 commit 都丟掉（ 完全 **不保留** ）。\r\n\r\n`--soft` 這個參數簡單解釋就是將之前的 commit 都丟掉，但 **保留** 你之前工作區的狀態。\r\n\r\n`--hard` 和 `--soft` 這兩個我覺得用文字不好說明，我建議大家自己可以動手玩玩看，就可以了解他們之間的差異。\r\n\r\n`--soft` 很適合使用在將多個無意義的 commit 合併成一個 commit。\r\n\r\n![alt tag](https://i.imgur.com/6RVutiK.jpg)\r\n\r\n版本號 ( ad41df36b7 ) 沒必要全部都寫，寫前幾位就可以了，Git 會自動去找。\r\n\r\n當你退回到某個版本，突然隔天後悔了，想恢復到之前的新版本該怎麼做呢?\r\n\r\n找不到新版本的 commit id 該怎麼辦呢?\r\n\r\n這時候就可以使用一個指令\r\n\r\n```cmd\r\ngit reflog\r\n```\r\n\r\n![alt tag](https://i.imgur.com/MaRlZZr.jpg)\r\n\r\n接著看你要回到哪個版本，再使用 git reset 即可。\r\n\r\n```cmd\r\ngit reset --hard 642e7af\r\n```\r\n\r\n有時候想消除( 覆蓋 )已經 push 出去的 commit，這時候我們可以使用\r\n\r\n```cmd\r\ngit push --force\r\n```\r\n\r\n或是更簡短的寫法\r\n\r\n```cmd\r\ngit push -f\r\n```\r\n\r\n可以強制 push。先回到某個版本，然後再強制 push。\r\n\r\n***注意！在多人專案共同開發時，盡量不要用 --force 這種方法，因為有時候會害到別人，建議可以使用 revert 。***\r\n\r\n因為上面這個原因，所以建議用另一種比較安全的方式\r\n\r\n```cmd\r\ngit push --force-with-lease\r\n```\r\n\r\n可以確保你沒有隨便丟掉別人的 commit。（ 如果有人比你早 commit push 上去，你就會無法 push 到 remote ）\r\n\r\n## checkout\r\n\r\n也請參考 [git switch](https://github.com/twtrubiks/Git-Tutorials#git-switch) 和 [git restore](https://github.com/twtrubiks/Git-Tutorials#git-restore).\r\n\r\n`git checkout -- file` 可以丟棄工作區的修改：\r\n\r\n```cmd\r\ngit checkout  -- hello.py\r\n```\r\n\r\n命令 git checkout -- hello.py 意思就是，把 hello.py 文件在工作區的修改全部撤銷 ( 丟棄 ) ，\r\n\r\n讓這個檔案回到最近一次 git commit 或 git add 時的狀態。\r\n\r\n![alt tag](https://i.imgur.com/SrCo4kH.jpg)\r\n\r\n當然也可以用 git reset 指令直接回到某個 commit。\r\n\r\n```cmd\r\ngit reset --hard xxxxxx\r\n```\r\n\r\n```cmd\r\ngit reset --hard 201f40604ec3b6fa8\r\n```\r\n\r\n## 刪除\r\n\r\n有兩種狀況，一種是確定要從版本庫中刪除該檔案，那就用命令 git rm 刪掉，並且 git commit：\r\n\r\n```cmd\r\nrm hello.py\r\ngit rm hello.py\r\ngit commit -m \"remove hello.py\"\r\n```\r\n\r\n![alt tag](https://i.imgur.com/sLMTDX7.jpg)\r\n\r\n另一種狀況是刪錯了，使用 git checkout 可以輕鬆還原檔案:\r\n\r\n```cmd\r\nrm hello.py\r\ngit checkout -- hello.py\r\n```\r\n\r\n![alt tag](https://i.imgur.com/5X2NcfS.jpg)\r\n\r\n## 新建與 合併 ( merge ) 分支 branch\r\n\r\n在說明分支 branch 之前，先給大家一個觀念。\r\n\r\n通常開發的時候，大家都是從 **master** 做一個分支 branch 出去，最後再 **merge** 回 master，\r\n\r\n為什麼要這麼做呢 ?  因為要確保大家都是使用最新的 **master**\r\n\r\n使用 git branch 指令查看目前的分支：\r\n\r\n```cmd\r\ngit branch\r\n```\r\n\r\n![alt tag](https://i.imgur.com/SVblXD2.jpg)\r\n\r\n首先創建一個分支，bug1 分支 ( 名稱可以隨便取 )，然後切換到 bug1 分支：\r\n\r\n```cmd\r\ngit branch bug1\r\ngit checkout bug1\r\n```\r\n\r\ngit branch bug1 為創造一個名稱為 bug1 的分支，\r\n\r\ngit checkout bug1 為切換到一個名稱為 bug1 的分支底下。\r\n\r\n![alt tag](https://i.imgur.com/JtGBHk4.jpg)\r\n\r\n以上兩行指令，相當於下列一行指令\r\n\r\n```cmd\r\ngit checkout -b bug1\r\n```\r\n\r\n(這邊教大家一個小技巧, 以下這個指令可以快速切換上一個分支, 和 `cd -` 概念一樣:exclamation:)\r\n\r\n```cmd\r\ngit checkout -\r\n```\r\n\r\n我們在 bug1 分支上進行任何修改操作，\r\n\r\n然後再把工作成果 ( 補充一下，修改任何內容後請記得使用 git add 指令和 git commit 指令 ) 合併到 master 分支上：\r\n\r\n```cmd\r\ngit checkout master\r\ngit merge bug1\r\n```\r\n\r\n![alt tag](https://i.imgur.com/pF4xDUE.jpg)\r\n\r\ngit checkout master 為切換到一個名稱為 master 的分支底下。\r\n\r\ngit merge bug1 指令用於合併 ( bug1分支 ) 指定分支到目前分支 ( master ) 底下。\r\n\r\n如果非常順利， git merge 的訊息裡會出現 Fast-forward，合併速度非常快。\r\n\r\n當然不是每次合併都能很順利的出現 Fast-forward，很多時候會出現衝突 CONFLICT 。\r\n\r\n如果順利合併 ( merge ) 完成後，就可以刪除 (本機) bug1 分支：\r\n\r\n```cmd\r\ngit branch -d dev\r\n```\r\n\r\n![alt tag](https://i.imgur.com/LmKKWxR.jpg)\r\n\r\n如果要丟掉一個沒有被合併過的分支，可以使用 git branch -D 分支名稱  強行刪除 (本機)。\r\n\r\n```cmd\r\ngit branch -D dev\r\n```\r\n\r\n那如果今天要刪除 remote 端的 branch 該怎麼辦呢:question:\r\n\r\n* [Youtube Tutorial - git 刪除查看遠端的分支 branch](https://youtu.be/0JQrT7nfm_c)\r\n\r\n```cmd\r\ngit push origin --delete {remote_branch}\r\n```\r\n\r\n補充，git branch 也可以修改名稱，而且 commit id 是不會改變的，使用方法也很簡單，\r\n\r\n可參考 git-branch [文件](https://git-scm.com/docs/git-branch#git-branch--m)，使用方法如下，\r\n\r\n```text\r\ngit branch -m \u003cname\u003e\r\n```\r\n\r\n原本的 b1 branch 分支的 log 如下，\r\n\r\n![alt tag](https://i.imgur.com/b1K1EUy.png)\r\n\r\n現在將 b1 branch 修改成 b2 branch，\r\n\r\n![alt tag](https://i.imgur.com/Twz5kRm.png)\r\n\r\n如果你仔細和剛剛的 log 比較，你會發現 log 的 commit id 是不會改變的，\r\n\r\n![alt tag](https://i.imgur.com/qMjqV3Z.png)\r\n\r\n## 使用特定 commit id 建立 branch\r\n\r\n有時候我們會想測試某個 commit 的狀態, 這時候可以直接利用 commit id 去建立一個 branch,\r\n\r\n方法如下,\r\n\r\n```cmd\r\ngit checkout -b new_branch \u003ccommit id\u003e\r\n```\r\n\r\n這樣就會依照你指定的 commit id 去建立出一個 branch.\r\n\r\n## 新建分支 branch 並 push\r\n\r\n相信大家有時候在 github 上面都會看到，如下圖，很多分支\r\n\r\n![alt tag](https://i.imgur.com/wrIdlzS.jpg)\r\n\r\n那我們要如何建立分支呢? 首先，我們先看下面這張圖\r\n\r\n![alt tag](https://i.imgur.com/3U092a1.jpg)\r\n\r\n有一個 v1 的分支，並且我在分支上增加一個 g.py 並且 commit。\r\n\r\n接下來要 **第一次** git push 的時候， 你會發現有錯誤提示\r\n\r\n請使用以下指令才是正確的\r\n\r\n```cmd\r\ngit push --set-upstream origin v1\r\n```\r\n\r\n也可以使用\r\n\r\n```cmd\r\ngit push -u origin v1\r\n```\r\n\r\n更多詳細說明可參考 [https://git-scm.com/docs/git-push#git-push--u](https://git-scm.com/docs/git-push#git-push--u)\r\n\r\n![alt tag](https://i.imgur.com/1fuS2VY.jpg)\r\n\r\n接下來你可以到網頁上看 ( 這裡用 bitbucket 當作範例 ) ，你會發現有分支 v1 了\r\n\r\n![alt tag](https://i.imgur.com/lOtzsk8.jpg)\r\n\r\n如果是第一次使用 git clone ，你會發現你只有 master 分支 ，\r\n\r\n這時候我們先查看遠端還有什麼分支，\r\n\r\n```cmd\r\ngit branch -r\r\n```\r\n\r\n```cmd\r\ngit branch --remote\r\n```\r\n\r\n`--remote` 或 `-r` 都可以.\r\n\r\n假設遠端有一個名稱為 develop 的分支，\r\n\r\n我們只要 checkout 到該分支底下就可以了\r\n\r\n```cmd\r\ngit checkout develop\r\n```\r\n\r\n## git switch\r\n\r\n[Youtube Tutorial - git switch 和 git restore 教學](https://youtu.be/JL_bSOGDR-k)\r\n\r\n請先確認目前的 git 版本, 更新方法可參考 [git 更新](https://github.com/twtrubiks/Git-Tutorials#git-%E6%9B%B4%E6%96%B0).\r\n\r\n在 git 2.23 版本開始, 增加了 `git switch` 和 `git restore`, 這兩個指令主要是\r\n\r\n要更清楚的劃分功能, 主要是來代替 `git checkout`.\r\n\r\n你其實可以想成 `git checkout` = `git switch` + `git restore`.\r\n\r\n官方文件可參考 [git-switch](https://git-scm.com/docs/git-switch)\r\n\r\n```cmd\r\ngit switch [\u003coptions\u003e] (-c|-C) \u003cnew-branch\u003e [\u003cstart-point\u003e]\r\n```\r\n\r\n切換到一個已經存在的 branch (如果該 branch 不存在則指令無效)\r\n\r\n```cmd\r\ngit switch \u003cnew-branch\u003e\r\n```\r\n\r\n建立 new-branch 並且切換到 new-branch 分支\r\n\r\n```cmd\r\ngit switch -c \u003cnew-branch\u003e\r\n```\r\n\r\n`-c` `--create`\r\n\r\n`-C` `--force-create`\r\n\r\n依照 commit_id (或前 N 的 commit 點) 建立 new-branch 並且切換到 new-branch 分支\r\n\r\n```cmd\r\ngit switch -c \u003cnew-branch\u003e \u003ccommit_id\u003e\r\ngit switch -c \u003cnew-branch\u003e HEAD~2\r\n```\r\n\r\n(這邊教大家一個小技巧, 以下這個指令可以快速切換上一個分支, 和 `cd -` 概念一樣:smile:)\r\n\r\n```cmd\r\ngit switch -\r\n```\r\n\r\n## git restore\r\n\r\n[Youtube Tutorial - git switch 和 git restore 教學](https://youtu.be/JL_bSOGDR-k)\r\n\r\n請先確認目前的 git 版本, 更新方法可參考 [git 更新](https://github.com/twtrubiks/Git-Tutorials#git-%E6%9B%B4%E6%96%B0).\r\n\r\n在 git 2.23 版本開始, 增加了 `git switch` 和 `git restore`, 這兩個指令主要是\r\n\r\n要更清楚的劃分功能, 主要是來代替 `git checkout`.\r\n\r\n你其實可以想成 `git checkout` = `git switch` + `git restore`.\r\n\r\n官方文件可參考 [git-restore](https://git-scm.com/docs/git-restore)\r\n\r\n以下兩個指令是相同的.\r\n\r\n```cmd\r\ngit checkout \u003cfile\u003e\r\ngit restore \u003cfile\u003e\r\n```\r\n\r\n還原目前資料夾全部的檔案\r\n\r\n```cmd\r\ngit restore .\r\n```\r\n\r\n還原目前資料夾底下結尾是 `*.py` 的全部檔案\r\n\r\n```cmd\r\ngit restore '*.py'\r\n```\r\n\r\n如果你的 `git` 版本比較新, 你應該會發現這個指令你以前好像沒看過:smile:\r\n\r\n![alt tag](https://i.imgur.com/IHqfVrn.png)\r\n\r\n```cmd\r\ngit restore --staged \u003cfile\u003e\r\n```\r\n\r\n## git pull\r\n\r\n通常在開始工作或要 push 之前，會先從遠端抓取分支，\r\n\r\n```cmd\r\ngit pull\r\n```\r\n\r\n如果有衝突，要先解衝突。\r\n\r\n這邊補充一下 `-C` 這個參數的意思, 它的意思代表指定 folder 路徑,\r\n\r\n有時候我們可能不想先 `cd` 進去資料夾, 再進行 pull, 這時候,\r\n\r\n就很適合使用它:smile:\r\n\r\n```cmd\r\ngit [-C \u003cpath\u003e] pull\r\n```\r\n\r\n舉例,\r\n\r\n```cmd\r\ncd git_folder\r\ngit pull\r\n```\r\n\r\n可以直接簡化為\r\n\r\n```cmd\r\ngit -C git_folder pull\r\n```\r\n\r\n## git fetch\r\n\r\n可以先簡單想成 **git pull = git fetch + git merge**\r\n\r\n我們先來看下面這張圖，  **git fetch + git merge**\r\n\r\n![alt tag](https://i.imgur.com/COuWByw.png)\r\n\r\n再看這張圖  **git pull**\r\n\r\n![alt tag](https://i.imgur.com/8FGuA75.png)\r\n\r\n這樣是不是清楚多了!!!\r\n\r\n多補充一個參數 `--prune`,\r\n\r\n* [Youtube Tutorial - git fetch 指令 prune 參數說明](https://youtu.be/ZMpMv1P1Q1Q)\r\n\r\n這個主要的功能是刪除 remote 無效的 branch,\r\n\r\n有時候明明已經把遠端的 branch 刪除, 但是你執行 `git branch --remote`,\r\n\r\n卻會發現你還看的到那些 branch 的分支 (但明明網頁上的分支已經被移除了:sweat:)\r\n\r\n常常會發生在 pull 端(非工作端)的機器 (如果不懂這句話的意思建議看影片說明:smile:)\r\n\r\n這時候就可以同步一下本機和遠端的分支, 使用以下的指令\r\n\r\n`git fetch --prune`\r\n\r\n## git rebase\r\n\r\n什麼是 rebase 呢 ? git rebase 就是避免多餘 ( 沒有意義 ) 的 merge !!! 先看看下面兩張圖\r\n\r\n補充 :\r\n\r\nck = checkout\r\n\r\nbr = branch\r\n\r\nst = status\r\n\r\ncm = commit\r\n\r\n可以自行設定。\r\n\r\n圖一\r\n\r\n![alt tag](https://i.imgur.com/mWY0f2J.png)\r\n\r\n圖二\r\n\r\n![alt tag](https://i.imgur.com/QVZc5P5.png)\r\n\r\n圖一 和 圖二 你喜歡看哪種圖 ?  答案很明顯，是 圖一 !!\r\n\r\n**rebase** 的目的主要就是盡量讓圖都像 圖一\r\n\r\n用講的大家一定霧煞煞，所以我直接實戰給大家看。\r\n\r\n先示範 **沒有使用 rebase** 的範例\r\n\r\n目前分支\r\n\r\n![alt tag](https://i.imgur.com/E0ahfnD.png)\r\n\r\n![alt tag](https://i.imgur.com/Lb4dB0V.png)\r\n\r\n以上說明 : 先建立 v1 branch，接著 add 後再 commit。\r\n\r\n假設現在又有人 push 了，以下模擬 pull ，自己加上一個 commit\r\n\r\n![alt tag](https://i.imgur.com/hFKX4yJ.png)\r\n\r\n以上說明 : 自己在 master 分支上加 t2.txt ， 並且commit ( 模擬 pull )\r\n\r\n接下來，切換到 master 分支下和 v1 branch 分支 合併，並且 push\r\n\r\n![alt tag](https://i.imgur.com/0sCH2Q1.png)\r\n\r\n你會發現，顯示出來的圖並不漂亮，如下圖\r\n\r\n![alt tag](https://i.imgur.com/zbIPdyb.png)\r\n\r\n示範 **使用 rebase** 的範例\r\n\r\n前面的部份基本上一樣\r\n\r\n![alt tag](https://i.imgur.com/E0ahfnD.png)\r\n\r\n![alt tag](https://i.imgur.com/Lb4dB0V.png)\r\n\r\n以上說明 : 先建立 v1 branch，接著 add 後再 commit。\r\n\r\n假設現在又有人 push 了，以下模擬 pull ，自己加上一個 commit\r\n\r\n![alt tag](https://i.imgur.com/hFKX4yJ.png)\r\n\r\n以上說明 : 自己在 master 分支上加 t2.txt ， 並且 commit ( 模擬 pull )\r\n\r\n***差異的部份***\r\n\r\n![alt tag](https://i.imgur.com/45ZXGiK.png)\r\n\r\n以上說明 : 先切換到 v1 分支，然後使用以下指令\r\n\r\n```cmd\r\ngit rebase master\r\n```\r\n\r\n![alt tag](https://i.imgur.com/Lpd9Kjr.png)\r\n\r\n以上說明 : 再切回 master 分支，並且使用 merge 合併 v1 分支，最後在 push\r\n\r\n你看~  是不是變的整齊又漂亮多了呢?\r\n\r\n![alt tag](https://i.imgur.com/1jBI7pw.png)\r\n\r\ngit rebase  就是將 master 的最新 commit 接回來，再補上自己分支的 commit。\r\n\r\n以上就是 git rebase 的介紹,\r\n\r\n另一種作法, 剛剛是必須切換到 v1 分支底下, 才執行指令,\r\n\r\n如果你現在在任何分支(像是 master 分支), 你可以使用以下指令\r\n\r\n```cmd\r\ngit rebase master v1\r\n```\r\n\r\n就是後面指定 v1, 執行完後它會自動幫你切換到 v1 分支上,\r\n\r\n結果都是一樣的.\r\n\r\n另外, 還有一個指令是 `git rebase --onto`\r\n\r\n```cmd\r\ngit rebase --onto \u003cnew base-commit\u003e \u003ccurrent base-commit\u003e\r\n```\r\n\r\n其實概念上都是一樣的, 就是你想要 rebase 到哪個 new base-commit 上,\r\n\r\n後面放 current base-commit 而已.\r\n\r\n可以搭配 git graph 觀看, 或是看 git 的文檔 `git rebase --help`\r\n\r\n## git rebase interactive\r\n\r\n小弟我當初年輕，一直以為 `git rebase` 就只是讓 commit log 看起來比較乾淨而已，結果無意間發現，\r\n\r\n`git rebase` 的 interactive 超強，所以，這邊就來介紹 `git rebase` 的強大功能 :smirk:\r\n\r\n以下是 git rebase interactive 可以使用的指令，這些說明是我從 git 中複製出來的，等等會顯示給大家看，\r\n\r\n```cmd\r\n# Commands:\r\n# p, pick = use commit\r\n# r, reword = use commit, but edit the commit message\r\n# e, edit = use commit, but stop for amending\r\n# s, squash = use commit, but meld into previous commit\r\n# f, fixup = like \"squash\", but discard this commit's log message\r\n# x, exec = run command (the rest of the line) using shell\r\n# d, drop = remove commit\r\n```\r\n\r\n如果大家想要更進一步的了解，請參考 [INTERACTIVE MODE](https://git-scm.com/docs/git-rebase#_interactive_mode)，\r\n\r\npick 沒什麼好講的，就使用這個 commit 而已:smile:\r\n\r\n### reword\r\n\r\n[Youtube Tutorial - git rebase interactive - reword - PART 1](https://youtu.be/JhY0rR2wQq0)\r\n\r\n```cmd\r\n# Commands:\r\n# p, pick = use commit\r\n# r, reword = use commit, but edit the commit message\r\n```\r\n\r\n以下為官方的說明\r\n\r\n```txt\r\nIf you just want to edit the commit message for a commit, replace the command \"pick\" with the command \"reword\".\r\n```\r\n\r\n說明已經很清楚了，就是可以編輯 commit message。\r\n\r\n( 不能修改 commit 內容，也就是 files 內容 )\r\n\r\n假設，現在我們有一個 git log 是這樣，\r\n\r\n![alt tag](https://i.imgur.com/6bWnJnK.png)\r\n\r\ncommit id 2659f65 有 Typo，正確的 commit message 應該是  add c.py 才對，\r\n\r\n所以現在要修正他，我們的目標 commit id 為 2659f65，指令為\r\n\r\n```cmd\r\ngit rebase -i \u003cafter-this-commit\u003e\r\n```\r\n\r\nafter-this-commit 這個是什麼意思:question:\r\n\r\n簡單說，就是要選當下的 commit id 的上一個，\r\n\r\n以這個例子來說，我們的目標 commit id 為 2659f65，但指令我們必須下\r\n\r\n```cmd\r\ngit rebase -i f0a761d\r\n```\r\n\r\n![alt tag](https://i.imgur.com/d15nGjx.png)\r\n\r\n這樣應該就很清楚了，總之，記得要選擇目標 commit id 的上一個就對了。\r\n\r\n當你按下 ENTER 之後，你應該會看到下圖\r\n\r\n![alt tag](https://i.imgur.com/4ISGcW1.png)\r\n\r\nA 的部份就是我們要修改的目標，B 的部分就是說明 ( 前面貼給大家看的東西 )，\r\n\r\n接著，按 i 進入編輯模式，然後將目標改成 r 或是 reword 都可以，接著輸入 `:wq`\r\n\r\n![alt tag](https://i.imgur.com/zPeHuDa.png)\r\n\r\n接著我們再按下 ENTER，會再跳出一次畫面，這時候，你就將 commit 訊息修改成\r\n\r\n正確的，將  add c.py Typo 修改為 add c.py\r\n\r\n![alt tag](https://i.imgur.com/brYbNqy.png)\r\n\r\n輸入 `:wq` 之後，再 ENTER ( 完成 )\r\n\r\n![alt tag](https://i.imgur.com/kitKqrm.png)\r\n\r\n我們再用 log 確認一下( 如下圖 )，的確修改成功了，成功將訊息修改為 add c.py，\r\n\r\n![alt tag](https://i.imgur.com/rWojGIu.png)\r\n\r\n這邊有個地方要和大家提一下，就是 commit id 會改變，我把改變的地方框出來給各位看，\r\n\r\n修改前\r\n\r\n![alt tag](https://i.imgur.com/6i6Wv35.png)\r\n\r\n修改後\r\n\r\n![alt tag](https://i.imgur.com/mvj96U2.png)\r\n\r\n簡單來說，就是目前 commit id 之後的 commit id 都會改變 ( 有點繞口 :sweat_smile: )\r\n\r\n這邊補充一下，只要你用了 rebase，就會看到類似下面的圖，\r\n\r\n![alt tag](https://i.imgur.com/iiDf44q.png)\r\n\r\norigin/master 就是指遠端 ( romote ) 的 repo，它是和你說你現在的 repo 已經和 origin/master\r\n\r\n不一樣了，所以，這時候你如果要 push，請使用 `git push --force-with-lease`。\r\n\r\n這邊可能有人會問，如果我希望修改第一個 commit 該怎麼辦 :question:\r\n\r\n這時候可以使用，\r\n\r\n```cmd\r\ngit rebase -i --root\r\n```\r\n\r\n### edit\r\n\r\n[Youtube Tutorial - git rebase interactive - edit - PART 2](https://youtu.be/TCKjQppHxxQ)\r\n\r\n```cmd\r\n# Commands:\r\n# p, pick = use commit\r\n# e, edit = use commit, but stop for amending\r\n```\r\n\r\n以下為官方的說明\r\n\r\n```txt\r\nBy 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.\r\n```\r\n\r\n簡單說，reword 只可以修改 commit message，而 edit 不只可以修改 commit message ，還可以修改 files 內容。\r\n\r\n先來看看下面這張圖\r\n\r\n![alt tag](https://i.imgur.com/9j0JnKw.png)\r\n\r\n這圖很明顯 add a.py -\u003e add b.py -\u003e add c.py -\u003e add d.py ，現在我想在 add c.py 和 add d.py 中再加一個東西，\r\n\r\n也就是變成 add a.py -\u003e add b.py -\u003e add c.py -\u003e add c1.py -\u003e add d.py  這樣。\r\n\r\n增加一個  add c1.py 的情境時就可以使用 edit 了，( 以下我就不說那麼詳細了，我直接講重點 )，\r\n\r\n先執行以下指令 ( 我們的目標是 a7ed6ff ，所以選他的上一個 commit id，也就是 f0a761d )\r\n\r\n```cmd\r\ngit rebase -i f0a761d\r\n```\r\n\r\n這次我們將 pick 修改成 e 或是 edit ( 如下圖 )\r\n\r\n![alt tag](https://i.imgur.com/bKrLIl3.png)\r\n\r\n當你按下 ENTER 之後，你會看到下圖，\r\n\r\n![alt tag](https://i.imgur.com/whkCzok.png)\r\n\r\nA 的部份是可以修改 commit message，\r\n\r\nB 的部份則是和你說當你修改 ( 滿足 ) 完畢，可以執行 `git rebase --continue`，\r\n\r\nA 的部份我們不做了，但我們現在來加工吧 ( 增加 c1.py )，\r\n\r\n首先，我們建立一個 c1.py 檔案，然後 `git add c1.py`，接著 commit 他 ( 如下圖 )\r\n\r\n![alt tag](https://i.imgur.com/frYBUfT.png)\r\n\r\n剛剛有說過了，當你滿足時，可執行 `git rebase --continue`，收工\r\n\r\n![alt tag](https://i.imgur.com/sjnEn0H.png)\r\n\r\n再用 log 確認一下，太神了 :satisfied: 成功加上去了\r\n\r\n![alt tag](https://i.imgur.com/irECwLH.png)\r\n\r\n### squash\r\n\r\n[Youtube Tutorial - git rebase interactive - squash fixup - PART 3](https://youtu.be/bfrZrbEHis0)\r\n\r\n```cmd\r\n# Commands:\r\n# p, pick = use commit\r\n# s, squash = use commit, but meld into previous commit\r\n```\r\n\r\n以下為官方的說明\r\n\r\n```text\r\n 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,\r\n```\r\n\r\n簡單說，你如果想要將多個 commit 合併成一個，使用 squash 就對了，( 以下我就不說那麼詳細了，我直接講重點 )，\r\n\r\n這次的目標是要將 commit id fc45824 以及 commit id a7ed6ff 合併起來 ( 如下圖 )\r\n\r\n![alt tag](https://i.imgur.com/v8XwOTN.png)\r\n\r\n先執行以下指令\r\n\r\n```cmd\r\ngit rebase -i f0a761d\r\n```\r\n\r\n接著你會看到下圖，我們將 fc45824 這個 cmmit 的 pick 修改成 s 或 squash\r\n\r\n( 他會合併他的前一個，也就是 a7ed6ff )\r\n\r\n![alt tag](https://i.imgur.com/rgWkvVp.png)\r\n\r\n( 如果你要合併多個 commit，就多個都改成 s 或 squash, 注意, 有順序性 :exclamation: :exclamation: )\r\n\r\n將著按下 ENTER，會看到下圖\r\n\r\n![alt tag](https://i.imgur.com/pB6yllA.png)\r\n\r\n這時候他已經合併了這兩個 commit，我們就可以輸入新的 commit message，\r\n\r\n這邊我們輸入 add c.py and c1.py\r\n\r\n![alt tag](https://i.imgur.com/m9E6KUp.png)\r\n\r\n再按 ENTER ( 成功 )\r\n\r\n![alt tag](https://i.imgur.com/X0O7I5H.png)\r\n\r\n可以再用 log 確認一下，我們成功將兩個 commit 合併了\r\n\r\n![alt tag](https://i.imgur.com/r53KIev.png)\r\n\r\nc.py 以及 c1.py 都存在，代表我們成功了 :satisfied:\r\n\r\n![alt tag](https://i.imgur.com/WhkLDGa.png)\r\n\r\n### fixup\r\n\r\n[Youtube Tutorial - git rebase interactive - squash fixup - PART 3](https://youtu.be/bfrZrbEHis0)\r\n\r\n```cmd\r\n# Commands:\r\n# p, pick = use commit\r\n# f, fixup = like \"squash\", but discard this commit's log message\r\n```\r\n\r\n以下為官方的說明\r\n\r\n```text\r\nomits the commit messages of commits with the \"fixup\" command.\r\n```\r\n\r\n其實這個和 squash 很像，通常如果我們要忽略一個 commit message 但保留 commit 的內容，我們就會使用 fixup，\r\n\r\n目標，這邊我們想要移除 fc45824 的個 commit ( 但保留 commit 的內容 )\r\n\r\n![alt tag](https://i.imgur.com/AFrd0UA.png)\r\n\r\n先執行以下指令\r\n\r\n```cmd\r\ngit rebase -i f0a761d\r\n```\r\n\r\n將 fc45824 的 pick 修改成 f 或 fixup ( 如下圖 )\r\n\r\n( 他會移除 fc45824 這個 commit message ，但保留 commit 的內容 )\r\n\r\n![alt tag](https://i.imgur.com/aDH1y1n.png)\r\n\r\n接著 ENTER，成功 rebase\r\n\r\n![alt tag](https://i.imgur.com/BMs2h8r.png)\r\n\r\n可以再用 log 確認一下，我們忽略了 add c1.py 這個 commit\r\n\r\n![alt tag](https://i.imgur.com/bgYJa6T.png)\r\n\r\n但是 c.py 以及 c1.py 都存在 ( 只忽略 commit message )，\r\n\r\n![alt tag](https://i.imgur.com/tYrB3F9.png)\r\n\r\n看到這裡，大家其實可以想一想 squash 和 fixup 真的非常類似，\r\n\r\n只不過 squash 可以修改 commit message。\r\n\r\n簡單一點，單純想要忽略某一個 commit message 時，使用 fixup，\r\n\r\n想要合併 commit 並修改 commit message 時，使用 squash。\r\n\r\n### exec\r\n\r\n[Youtube Tutorial - git rebase interactive - exec drop - PART 4](https://youtu.be/u8imRiiSyzk)\r\n\r\n```cmd\r\n# Commands:\r\n# p, pick = use commit\r\n# x, exec = run command (the rest of the line) using shell\r\n```\r\n\r\n以下為官方的說明\r\n\r\n```text\r\nYou 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\")\r\n```\r\n\r\n這個功能我比較少用，但還是說一下，簡單說，就是他可以用來 check 你的\r\n\r\nrebase 改動是不是影響到整體 ( 用 exec command 確認 )。\r\n\r\n聽不太懂 :question: 沒關係，假如我今天做了一大堆的 rabase 更動，但我想確認我這樣做了之後，\r\n\r\n對整體是不是有影響，也就是可以在更動時，順便跑你的 test 去確認整體是正常 work。\r\n\r\n還是聽不懂 :question: 也沒關係，我用一個範例給大家看\r\n\r\n![alt tag](https://i.imgur.com/iu1bEOw.png)\r\n\r\n如上圖，假如我想要在我更動中做一些 test 去確保我的更動不會影響整體，\r\n\r\n( 雖然這邊都是 pick，也就是沒改動，但方便說明，大家請自行想像有改動:sweat_smile: )\r\n\r\n![alt tag](https://i.imgur.com/2c9ycmS.png)\r\n\r\nA 的部份 echo \"test sucess\" 這個自然不用有問題，\r\n\r\n但是 B 的部分就會出問題，因為根本沒有 error 這個指令，\r\n\r\n當如果執行到 shell 有錯誤時，他會停下來，讓你修正，\r\n\r\n如下圖，我們停在了 add c.py 這個 commit 上，因為接下來得 test error 了\r\n\r\n![alt tag](https://i.imgur.com/yVB3naC.png)\r\n\r\n這時候我們可以修正問題，修正完了之後，再執行 `git rebase --continue`。\r\n\r\n![alt tag](https://i.imgur.com/YBD0d9V.png)\r\n\r\n這個功能我想應該是讓你去邊修改邊跑你自己的 test，確保改動都正常。\r\n\r\n### drop\r\n\r\n[Youtube Tutorial - git rebase interactive - exec drop - PART 4](https://youtu.be/u8imRiiSyzk)\r\n\r\n```cmd\r\n# Commands:\r\n# p, pick = use commit\r\n# d, drop = remove commit\r\n```\r\n\r\n以下為官方的說明\r\n\r\n```text\r\nTo drop a commit, replace the command \"pick\" with \"drop\", or just delete the matching line.\r\n```\r\n\r\n這個就簡單多了，移除這個 commit ( 包含 commit 內容 )，\r\n\r\n假設我們的 log 如下，\r\n\r\n![alt tag](https://i.imgur.com/zz5arVp.png)\r\n\r\n這次的目標是移除 f0a761d 和 980bd9a 和 1539219 這些 commit，\r\n\r\n先執行以下指令\r\n\r\n```cmd\r\ngit rebase -i 8f13aaa\r\n```\r\n\r\n將 pick 修改成 d 或 drop ( 如下圖 )\r\n\r\n![alt tag](https://i.imgur.com/Goc1LH1.png)\r\n\r\n按 ENTER 之後，再用 log 確認一下，\r\n\r\n![alt tag](https://i.imgur.com/u7z2Y3U.png)\r\n\r\n從上圖可以發現，我們已經成功的移除 f0a761d 和 980bd9a 和 1539219 這些 commit，\r\n\r\n並且也看到 commit 內容也都被移除了，只剩下 a.py 而已。\r\n\r\n## git pull 補充\r\n\r\n既然介紹完了 `git fetch` 以及 `git rebase` 之後，接下來我要再補充一些 `git pull` 額外的 options 參數\r\n\r\n```cmd\r\ngit pull [\u003coptions\u003e] [\u003crepository\u003e [\u003crefspec\u003e…​]]\r\n```\r\n\r\n更多詳細指令可參考 [https://git-scm.com/docs/git-pull#_options](https://git-scm.com/docs/git-pull#_options)。\r\n\r\n這裡簡單整理一下，\r\n\r\n```cmd\r\ngit pull = git fetch + git merge\r\ngit pull --rebase = git fetch + git rebase\r\n```\r\n\r\n在 [git-rebase](https://github.com/twtrubiks/Git-Tutorials#git-rebase) 中已經讓大家了解到使用 git-rebase 可以讓 code review 的人\r\n\r\n看起來比較舒服，所以就使用 `git pull --rebase` 吧 ( 前提是你要知道你在幹嘛 :smile: )。\r\n\r\n這邊我模擬 `git pull` 以及 `git pull --rebase` 的差異，順便加上衝突的情況，因為步驟蠻多的，\r\n\r\n所以如果你想了解更多他的概念，請參考以下手把手教學，\r\n\r\n[Youtube Tutorial - git pull vs git pull --rebase](https://youtu.be/8h0K-2OaeSk)\r\n\r\n使用 `git pull` 後的結果，code review 的人一定翻桌 ( 如下圖 ) :triumph:\r\n\r\n這邊我有順便模擬衝突的時候，你會發現如果使用 `git pull` 會多一個 commit (也就是下方的 \"fix conflict\")。\r\n\r\n![alt tag](https://i.imgur.com/CNgKR3y.png)\r\n\r\n使用 `git pull --rebase` 後的結果，code review 的人表示溫馨  ( 如下圖 ) :innocent:\r\n\r\n這邊我有順便模擬衝突的時候，你會發現如果使用 `git pull --rebase` 並不會像剛剛一樣多了一個 commit，\r\n\r\n原因是因為當我們使用 `git pull --rebase` 造成衝突時，修好衝突的內容之後，git add xxxx，接著我們會\r\n\r\n直接執行 `git rebase --continue`。\r\n\r\n![alt tag](https://i.imgur.com/RKMo9ue.png)\r\n\r\n假設今天你執行了 `git pull --rebase` 之後，發現很難受 :fearful:，想要取消，\r\n\r\n直接執行 `git rebase --abort` 即可回到之前的狀態。\r\n\r\n額外補充小技巧,\r\n\r\n* [Youtube Tutorial - git autostash 參數說明](https://youtu.be/kg2PyZr7l5k)\r\n\r\n說明 `--autostash`,\r\n\r\n一般來說, 如果我們工作到一半, 突然想要直接 `git pull --rebase`, 又不想 commit,\r\n\r\n流程大約會像下面這樣\r\n\r\n```cmd\r\ngit stash # 將目前的改動存進去 stash 中\r\ngit pull --rebase\r\ngit stash pop # 將之前的改動從 stash 中 pop 出來\r\n# 如果有衝突再去解決衝突\r\n```\r\n\r\n但如果每次都要執行這麼多指令其實會有點煩 :sweat:\r\n\r\n但可以透過一個參數來解決, 也就是\r\n\r\n`git pull --rebase --autostash`\r\n\r\n以上這段指令基本上就是幫你執行了剛剛上面那一串的東西,\r\n\r\n如果有衝突, 就再修正衝突即可 :smile:\r\n\r\n## git-cherry-pick\r\n\r\n看影片會更清楚，手把手帶大家動手做 [Youtube Tutorial - git-cherry-pick](https://youtu.be/x3UtKUvlDdI)\r\n\r\ngit-cherry-pick 這個指令大家可能會比較陌生 :confused:\r\n\r\n沒關係，我們先來看 [官方](https://git-scm.com/docs/git-cherry-pick) 的說明\r\n\r\n```text\r\ngit-cherry-pick - Apply the changes introduced by some existing commits\r\n```\r\n\r\n看完官方說明還是 :question: :question: :question:\r\n\r\n沒關係，我來假設一個情境 ( 理解完它你就了解了 git-cherry-pick 的用途了 )，\r\n\r\n假設現在 master 分支的 log 如下圖\r\n\r\n![alt tag](https://i.imgur.com/cMcn6yE.png)\r\n\r\n然後有一個 v1 的分支 log 如下圖\r\n\r\n![alt tag](https://i.imgur.com/OZ7JLke.png)\r\n\r\n現在我希望 merge v1 分支中的 14dee93 - add d.py 這個 commit\r\n\r\n( 因為 14dee93 這個 commit 實在太棒了或是因為某些原因只需要這個 commit )\r\n\r\n遇到上述這種情況，就很適合使用 git-cherry-pick，也就是說我想要其他分支中的某幾個 commit 而已，\r\n\r\n不需要全部，換句話說，就是撿其他分支中的 commit 過來使用。\r\n\r\n了解了適合的使用情境，接下來我們就來實戰 :smirk:\r\n\r\n首先，我想要 v1 分支中的 14dee93 - add d.py 這個 commit，\r\n\r\n所以我先切到 master 分支，接著執行\r\n\r\n```cmd\r\ngit cherry-pick 14dee93\r\n```\r\n\r\n如果你想要一次撿很多的分支過來也是可以，直接使用空白隔開即可\r\n\r\n```cmd\r\ngit cherry-pick 14dee93 xxxxxx xxxxxx xxxxxx xxxxx\r\n```\r\n\r\n如果你想一次撿一個區間的 commits, 可以使用以下的指令\r\n\r\n```cmd\r\ngit cherry-pick A^..B\r\n```\r\n\r\n(A 和 B 代表你的 commits id)\r\n\r\n如果沒有衝突，就會看到如下圖\r\n\r\n![alt tag](https://i.imgur.com/YITXxMk.png)\r\n\r\n再觀看一下 master 的 log\r\n\r\n![alt tag](https://i.imgur.com/iGEIDZL.png)\r\n\r\n你會發現我們成功把 v1 分支中的 14dee93 - add d.py 這個 commit 拿過來\r\n\r\n使用了，但現在它的 commit id 卻是 ab70429，這個是正常的，因為它需要\r\n\r\n重新新計算 :smile:\r\n\r\n其實，你會發現 git-cherry-pick 沒有想像中的困難 :satisfied:\r\n\r\n在 cherry-pick 時，難免會遇到衝突，這邊我就再多做一個衝突的範例，\r\n\r\n假設 master 的 log 如下\r\n\r\n![alt tag](https://i.imgur.com/pttbQ5U.png)\r\n\r\nv1 分支中的 log 如下，我想要它的 3a2f29a - add c.py and print world 這個 commit\r\n\r\n![alt tag](https://i.imgur.com/RFibHS6.png)\r\n\r\nv2 分支中的 log 如下，我想要它的  553587b - add f.py 這個 commit\r\n\r\n![alt tag](https://i.imgur.com/I6L2Fwq.png)\r\n\r\n接下來我們就切回 master，然後 cherry-pick 這兩個 commit，\r\n\r\n這時候你會發現，它衝突了 :fearful:\r\n\r\n![alt tag](https://i.imgur.com/fAtQET0.png)\r\n\r\n使用 `git status` 看一下狀態，其實 A 的部分都教你如何解衝突了\r\n\r\n![alt tag](https://i.imgur.com/J8ZpPng.png)\r\n\r\n首先，我們先將 c.py 修正後，執行 `git add c.py`，接著再按照 A 的部份\r\n\r\n執行 `git cherry-pick --continue`，就時候會跳出一個編輯視窗，\r\n\r\n![alt tag](https://i.imgur.com/giylVAL.png)\r\n\r\n輸入完 commit message 之後，再輸入 `wq`，就會看到下圖\r\n\r\n![alt tag](https://i.imgur.com/rA8wMbO.png)\r\n\r\n最後，再觀看 log，\r\n\r\n![alt tag](https://i.imgur.com/lEP648c.png)\r\n\r\n我們成功將我們要的 commit merge 到我們的 master 分支上了 :kissing_smiling_eyes:\r\n\r\n想了解更多的使用方法，可參考官方文件\r\n[https://git-scm.com/docs/git-cherry-pick](https://git-scm.com/docs/git-cherry-pick)。\r\n\r\n## git revert\r\n\r\n假設我 commit history 為 A1 -\u003e A2 -\u003e A3 -\u003e A4 -\u003e A5 -\u003e A6\r\n\r\n我現在想要回 A4 這個 commit , 這時候我就可以使用 git revert ！！\r\n\r\n先 revert A6\r\n\r\n```cmd\r\ngit revert A6\r\n```\r\n\r\n再 revert A5\r\n\r\n```cmd\r\ngit revert A5\r\n```\r\n\r\n假如你再看現在的 commit history , 他會長的像這樣\r\n\r\nA1 -\u003e A2 -\u003e A3 -\u003e A4 -\u003e A5 -\u003e A6 -\u003e A6_revert -\u003e A5_revert\r\n\r\n這時候，其實你的 commit 就是在 A4 這個位置 。\r\n\r\n使用 git revert 的好處，就是可以保留 commit history, 萬一你又後悔了，\r\n\r\n也可以在 revert 回去。\r\n\r\n如果你想要 revert 最新的 commit, 只需要使用 HEAD\r\n\r\n```cmd\r\ngit revert HEAD\r\n```\r\n\r\n## 解決衝突\r\n\r\n在進行合併的時候，有時候會顯示出 **衝突conflicts** ，這時候就必須手動解決衝突後再送出。\r\n\r\n通常我目前最容易遇到衝突 conflicts ，就是使用 pull 這個指令的時候\r\n\r\n![alt tag](https://i.imgur.com/Eph0Vw1.jpg)\r\n\r\n仔細看這張圖，如果使用**pull**這個指令，會幫你 **自動 merge** ( 如圖裡的 Auto-merging Hello.py )，\r\n\r\n然後接著看 CONFLICT ( content ) : Merge conflict in Hello.py ，又說 Automatic merge failed，\r\n\r\n就是告訴你， Hello.py 這個檔案有衝突，然後你必須手動下去解決衝突。\r\n\r\ngit status 可以告訴我們衝突的文件。\r\n\r\n![alt tag](https://i.imgur.com/vlVcXn8.jpg)\r\n\r\n打開衝突文件我們會看到 Git 用 \u003c\u003c\u003c\u003c\u003c\u003c\u003c，=======，\u003e\u003e\u003e\u003e\u003e\u003e\u003e 標記出不同分支的內容，我們修改完畢後再提交：\r\n\r\n![alt tag](https://i.imgur.com/rlPOaxn.jpg)\r\n\r\n通常我們會手動下去修改衝突 conflicts，然後再加個 commit\r\n\r\n```cmd\r\ngit add Hello.py\r\ngit commit -m \"conflict fixed\"\r\n```\r\n\r\n### 假設今天我們想要放棄這個 merge 我們該怎麼做呢 ？\r\n\r\n```cmd\r\ngit merge --abort\r\n```\r\n\r\n或\r\n\r\n```cmd\r\ngit reset --hard HEAD\r\n```\r\n\r\n可以取消這次的 merge 回到 merge 前。\r\n\r\n## git stash 指令\r\n\r\n* [Youtube Tutorial - git stash 指令](https://youtu.be/CN065MNHtMY)\r\n\r\n很多時候，我們正在開發一個新功能又或是 debug，然後突然有一個功能需要緊急修正，\r\n\r\n但你又不想 commit 現在的狀況，因為根本沒意義，事情只做了一半，這時候 **stash**\r\n\r\n這個實用的指令就派上用場了。\r\n\r\n舉個例子，假設我們改了 A.py 和 B.py 這兩個檔案\r\n\r\n![alt tag](https://i.imgur.com/7xX0T1T.jpg)\r\n\r\n然後，現在突然有一個 bug 必須馬上(立刻)處理，\r\n\r\n但是，啊我手上的事情還沒做完阿~~~~\r\n\r\n這時候，可以利用以下指令\r\n\r\n```cmd\r\ngit stash\r\n```\r\n\r\n![alt tag](https://i.imgur.com/cYCH8mV.jpg)\r\n\r\n假如你想要更清楚自己這次的 stash 原因是什麼，或是這是正在開發什麼功能\r\n可以使用以下指令\r\n\r\n範例\r\n\r\n```cmd\r\ngit stash save \"我是註解\"\r\n```\r\n\r\n```cmd\r\ngit stash save -u \"feature\"\r\n```\r\n\r\n參數說明\r\n\r\n`-u` | `--include-untracked`\r\n\r\n`-a` | `--all`\r\n\r\n![alt tag](https://i.imgur.com/nGS11Px.jpg)\r\n\r\n接下來你可以使用 status 指令，你會發現變乾淨了\r\n\r\n![alt tag](https://i.imgur.com/Xf53GfM.jpg)\r\n\r\n並且可以使用下列的指令來觀看 stash 裡面的東西\r\n\r\n```cmd\r\ngit stash list\r\n```\r\n\r\n![alt tag](https://i.imgur.com/jQPiYiX.jpg)\r\n\r\n然後你很努力地解決這個 bug，commit 完之後，\r\n可以再使用下列的指令把 stash 取回來，這指令取回後也會刪除 stash\r\n\r\n```cmd\r\ngit stash pop\r\n```\r\n\r\n假設今天你有很多的 stash，你可以指定，如下 (選自己喜歡的用法)\r\n\r\n```cmd\r\ngit stash pop 0\r\ngit stash pop stash@{0}\r\n```\r\n\r\n![alt tag](https://i.imgur.com/zVF7no2.jpg)\r\n\r\n你會發現剛剛的東西回來了~\r\n\r\n如果你希望使用 stash 取回之後，不希望刪除 stash ，可以使用下列的指令\r\n\r\n```cmd\r\ngit stash apply\r\n```\r\n\r\n如下圖，你可以發現取回後， stash 並沒有被刪除\r\n\r\n![alt tag](https://i.imgur.com/w3Ip3iW.jpg)\r\n\r\n如果你只是想要刪除暫存，可以使用下列的指令\r\n\r\n```cmd\r\ngit stash clear\r\n```\r\n\r\n從下圖可以發現，stash 裡面的東西被我們刪除了\r\n\r\n![alt tag](https://i.imgur.com/PvzufbQ.jpg)\r\n\r\n如果你想丟棄指定的 stash，可以使用 (選自己喜歡的用法)\r\n\r\n```cmd\r\ngit stash drop 0\r\ngit stash drop stash@{0}\r\n```\r\n\r\n## git tag\r\n\r\n[Youtube Tutorial - git tag 教學](https://youtu.be/azciLlpr3Gs)\r\n\r\n查看 tag\r\n\r\n```cmd\r\ngit tag\r\n```\r\n\r\n![alt tag](https://i.imgur.com/8f6zGfm.png)\r\n\r\n指定關鍵字\r\n\r\n```cmd\r\ngit tag -l \"v1.*\"\r\n```\r\n\r\n`-l` `--list`\r\n\r\ngit tag 有 輕量級標籤(lightweight tag) 和 附註標籤(annotated tag).\r\n\r\n輕量級標籤(lightweight tag)\r\n\r\n如果想要建立一個輕量級的標籤，請不要指定 `-a` `-s`(GPG-signed) `-m`\r\n\r\n```cmd\r\ngit tag tag_name [commit_id]\r\n```\r\n\r\n如果只使用 `git tag tag_name` , 而沒加上後面 Commit id,\r\n\r\n則會自動把 tag 放在目前的這個 Commit id 上.\r\n\r\n顯示註解\r\n\r\n```cmd\r\ngit show v1.1-light\r\n```\r\n\r\n附註標籤(annotated tag)\r\n\r\n```cmd\r\ngit tag -a v1.1 -m \"version 1.1\"\r\n```\r\n\r\n`-a` 就是標籤名稱 `--annotate`\r\n\r\n`-m` 代表該標籤說明(註解)\r\n\r\n在指定的 commit 上設 tag\r\n\r\n```cmd\r\ngit tag -a v1.2 -m \"version 1.1\" [commit_id]\r\n```\r\n\r\n顯示註解\r\n\r\n```cmd\r\ngit show v1.1\r\n```\r\n\r\n輕量級標籤(lightweight tag) 和 附註標籤(annotated tag) 的差別就是是否能看到更多的細節,\r\n\r\n附註標籤(annotated tag) 多了更多的資訊.\r\n\r\n輕量級標籤(lightweight tag) 如下\r\n\r\n![alt tag](https://i.imgur.com/4cUkdyQ.png)\r\n\r\n附註標籤(annotated tag) 如下\r\n\r\n![alt tag](https://i.imgur.com/DQWB1uh.png)\r\n\r\n當你執行 `git push` 預設是不會將 tag 推到 remote.\r\n\r\n需要執行以下的指令, push tag 到 remote 端\r\n\r\n```cmd\r\ngit push origin [tagname]\r\n```\r\n\r\n一次 push 很多 tags (將會把你全部不在 remote 端的 tag 都 push 上去.)\r\n\r\n```cmd\r\ngit push origin --tags\r\n```\r\n\r\n當其他人執行 `git clone` 或 `git fetch` 就可以拿到這些 tags.\r\n\r\n移除本地 tag\r\n\r\n```cmd\r\ngit tag -d [tagname]\r\n```\r\n\r\n刪除 remote tag\r\n\r\n```cmd\r\ngit push --delete origin [tagname]\r\n```\r\n\r\n## git show\r\n\r\n一般來說，我只用他來看這個 commit 修改了哪些東西\r\n\r\n```cmd\r\ngit show \u003ccommit ID\u003e\r\n```\r\n\r\n![alt tag](https://i.imgur.com/rjpl8VL.png)\r\n\r\n```cmd\r\ngit show [\u003coptions\u003e] [\u003cobject\u003e…​]\r\n```\r\n\r\n其他更詳細的介紹，請參考 [https://git-scm.com/docs/git-show](https://git-scm.com/docs/git-show)\r\n\r\n## git diff\r\n\r\n以下為官方說明\r\n\r\n```text\r\n Show changes between commits, commit and working tree, etc\r\n```\r\n\r\n這邊舉幾個例子，\r\n\r\n檔案還沒進入暫存區 ( Stage )，也就是執行 git add xxx 之前，\r\n\r\n可以看做了那些修改，\r\n\r\n![alt tag](https://i.imgur.com/nj5Gz5P.png)\r\n\r\n也可以看 commits 之間的差異\r\n\r\n![alt tag](https://i.imgur.com/JMJ48jO.png)\r\n\r\n其他更詳細的介紹，請參考 [https://git-scm.com/docs/git-diff](https://git-scm.com/docs/git-diff)\r\n\r\n## git diff-tree\r\n\r\ngit-diff-tree - Compares the content and mode of blobs found via two tree objects\r\n\r\n比較兩個 blob (commit) 的差異.\r\n\r\n文件可參考 [git-diff-tree](https://git-scm.com/docs/git-diff-tree/en)\r\n\r\n直接看範例\r\n\r\n```cmd\r\ngit diff-tree -r --no-commit-id --name-status -a --diff-filter=ACDMRT \u003c其中一個commit id\u003e \u003c要比較的commit-id\u003e \u003e changes.txt\r\n```\r\n\r\n`-r` 代表 Recurse into sub-trees.\r\n\r\n`--no-commit-id` This flag suppressed the commit ID output.\r\n\r\n`--name-status` Show only the name(s) and status of each changed file.\r\n\r\n`--text` `-a` Treat all files as text.\r\n\r\n`--diff-filter=[(A|C|D|M|R|T|U|X|B)…​[*]]`\r\n\r\nAdded (A), Copied (C), Deleted (D), Modified (M), Renamed (R),\r\n\r\nhave their type (i.e. regular file, symlink, submodule, …​)\r\n\r\nchanged (T), are Unmerged (U), are Unknown (X),\r\n\r\nor have had their pairing Broken (B).\r\n\r\n執行後打開 change.txt 會看到差異的檔案名稱.\r\n\r\n```text\r\nM\taddons/account/i18n/account.pot\r\nM\taddons/account/i18n/ar.po\r\nM\taddons/account/i18n/az.po\r\nM\taddons/account/i18n/be.po\r\nM\taddons/account/i18n/bg.po\r\nM\taddons/account/i18n/ca.po\r\nM\taddons/account/i18n/cs.po\r\n......\r\n```\r\n\r\n## git archive\r\n\r\n延續上面的例子, 如果想要打包不同的 commit 之間的差異檔案 (不想要整包匯出, 因為太大了, 只想找出差異檔案),\r\n\r\n這時候可以搭配 archive 指令, 範例如下\r\n\r\n```cmd\r\ngit archive --format=zip --output=files-diff.zip HEAD $(git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT \u003c其中一個commit id\u003e \u003c要比較的commit-id\u003e)\r\n```\r\n\r\n這樣匯出來的 zip, 就是這兩個 commit 之間差異的完整檔案.\r\n\r\n## git grep\r\n\r\n以下為官方說明\r\n\r\n```text\r\ngit-grep - Print lines matching a pattern\r\n```\r\n\r\n簡單說，就是可以幫你找出符合的 pattern，舉個例子，我希望找出內容\r\n\r\n有包含 hello 這個 pattern 的檔案，這時候，就可以執行以下指令\r\n\r\n```cmd\r\ngit grep \"hello\"\r\n```\r\n\r\n![alt tag](https://i.imgur.com/t5vxvvp.png)\r\n\r\n會顯示出該 pattern 在個檔案以及哪段程式碼有用到。\r\n\r\n其他更詳細的介紹，請參考 [https://git-scm.com/docs/git-grep](https://git-scm.com/docs/git-grep)\r\n\r\n## git clean\r\n\r\n刪除未被追蹤的檔案,\r\n\r\n`git clean -n`\r\n\r\n`-n, --dry-run` Don’t actually remove anything, just show what would be done\r\n\r\n這個指定是告訴你會刪除哪些資料, 不會真的刪除.\r\n\r\n範例如下,\r\n\r\n```cmd\r\n❯ git status\r\nOn branch master\r\n\r\nNo commits yet\r\n\r\nUntracked files:\r\n  (use \"git add \u003cfile\u003e...\" to include in what will be committed)\r\n        test.py\r\n\r\nnothing added to commit but untracked files present (use \"git add\" to track)\r\n\r\n❯ git clean -n\r\nWould remove test.py\r\n```\r\n\r\n如果你執行以下的指令, 就會真的刪除,\r\n\r\n`git clean -df`\r\n\r\n詳細說明可使用 `git clean --help` 觀看,\r\n\r\n範例如下,\r\n\r\n```cmd\r\n❯ git status\r\nOn branch master\r\n\r\nNo commits yet\r\n\r\nUntracked files:\r\n  (use \"git add \u003cfile\u003e...\" to include in what will be committed)\r\n        test.py\r\n\r\nnothing added to commit but untracked files present (use \"git add\" to track)\r\n\r\n❯ git clean -df\r\nRemoving test.py\r\n\r\n❯ git status\r\nOn branch master\r\n\r\nNo commits yet\r\n\r\nnothing to commit (create/copy files and use \"git add\" to track)\r\n```\r\n\r\n還記得前面介紹的 `git reset` 指令嗎, 基本上它可以搭配 `git clean` 一起使用,\r\n\r\n`git clean` 影響沒有被 track 的檔案\r\n\r\n`git reset` 影響有被 track 的檔案\r\n\r\n結合以上, 可以回到一個指定的 commit 乾淨的狀態,\r\n\r\n```cmd\r\ngit reset --hard HEAD\r\ngit clean -df\r\ngit status\r\n```\r\n\r\n建議大家自己操作一下.\r\n\r\n## git Submodule\r\n\r\n由於這個內容稍微比較多，所以我另外寫了一篇，\r\n\r\n* [Youtube Tutorial PART 1 - git Submodule tutorial - how to create submodule](https://youtu.be/IDMWLJCbCGo)\r\n\r\n* [Youtube Tutorial PART 2 - git Submodule tutorial - how to update submodule](https://youtu.be/ogZoZOVyAYI)\r\n\r\n* [Youtube Tutorial PART 3 - git Submodule tutorial - how to clone submodule](https://youtu.be/f5_O5Iu6pJo)\r\n\r\n* [Youtube Tutorial PART 4 - git Submodule tutorial - how to remove submodule](https://youtu.be/imndFN7AvFA)\r\n\r\n[git Submodule tutorial :memo:](https://github.com/twtrubiks/Git-Tutorials/blob/master/git_submodule_turorial.md)\r\n\r\n## git Subtree\r\n\r\n由於這個內容稍微比較多，所以我另外寫了一篇，\r\n\r\n* [Youtube Tutorial PART 1 - git subtree tutorial - how to create subtree](https://youtu.be/kEvgK2gH_vg)\r\n\r\n* [Youtube Tutorial PART 2 - git subtree tutorial - how to push subtree](https://youtu.be/Df3zc1VOqN8)\r\n\r\n* [Youtube Tutorial PART 3 - git subtree tutorial - how to pull/create subtree](https://youtu.be/dE-D2yrD4ws)\r\n\r\n[git subtree tutorial :memo:](https://github.com/twtrubiks/Git-Tutorials/blob/master/git_subtree_turorial.md)\r\n\r\n## git 其他設定\r\n\r\n我們已經設定了 user.name 以及 user.email ，但 Git 上其實還有很多可設定的東西\r\n\r\n有時候，我們必須把某些檔案 ( 文件夾 ) 放到 Git 工作目錄中，但又不能提交它們，\r\n\r\n像是密碼設定或是編譯器 IDE 產生出來的東西之類的，\r\n\r\n每次 git status 都會看到紅紅的 Untracked files ，通常會覺得有點煩......\r\n\r\n這問題 Git 也幫我們想過，只要在 Git 工作區的根目錄下新建一個特殊的 **.gitignore** 文件 ，\r\n\r\n然後把要忽略的文件 ( 檔案 ) 名稱輸入進去， Git 就會自動忽略這些文件。\r\n\r\n當然不需要自己從頭寫 .gitignore 文件， GitHub 已經幫我們準備了一些文件 [gitignore](https://github.com/github/gitignore)\r\n\r\n**.gitignore** 檔案直接放在目錄底下即可\r\n\r\n![alt tag](https://i.imgur.com/8rHPsII.jpg)\r\n\r\n### .gitignore 檔案格式範例\r\n\r\n![alt tag](https://i.imgur.com/W3cxk9r.jpg)\r\n\r\n### .gitignore (Temporarily and Permanently)\r\n\r\n主要分 暫時(Temporarily) 和 永久(Permanently) 的ignore，\r\n\r\n* Temporarily ignore\r\n\r\n適合使用在 settings 的檔案，有時候我們在開發的時候，都會有自己的設定，\r\n\r\n但這個設定未必是大家都需要的，這時候就可以暫時先忽略這個檔案的改變。\r\n\r\n暫時忽略某個檔案\r\n\r\n```cmd\r\ngit update-index --skip-worktree \u003cfile\u003e\r\n```\r\n\r\n恢復(Resume)暫時忽略某個檔案\r\n\r\n```cmd\r\ngit update-index --no-skip-worktree \u003cfile\u003e\r\n```\r\n\r\n* Permanently ignore\r\n\r\n這邊補充一個情境，假設今天 file 這個檔案已經被 commit 到 git 中了，\r\n\r\n但是我想把他加入 .gitignore，這樣該怎麼辦 :question:\r\n\r\n如果你在 .gitignore 中加入 file，你會發現還是沒有被 ignore :confused:\r\n\r\n![alt tag](https://i.imgur.com/o922paa.png)\r\n\r\n這時候，正確的做法應該是要先執行已下指令，\r\n\r\n```cmd\r\ngit rm --cached \u003cfile\u003e\r\n```\r\n\r\n執行完後再 commit 即可 ( 檔案不會從系統上刪除，只是要更新 git 的 index 而已 )\r\n\r\n![alt tag](https://i.imgur.com/RJZ08OQ.png)\r\n\r\n這時候可以再嘗試更新 file 的內容，你會發現它成功被 ignore 了 :smile:\r\n\r\n### git alias\r\n\r\n有時候常常手殘 key 錯指令或是記不起來\r\n\r\n如果我們打 git st 就表示 git status 那該有多棒!!!\r\n\r\n所以我們可以自己設定，讓 Git 以後打 **git st = git status**\r\n如下圖，原本不能使用 git st ，設定完之後就可以使用了。\r\n\r\n```cmd\r\ngit config --global alias.st status\r\n```\r\n\r\n![alt tag](https://i.imgur.com/4NNasgB.jpg)\r\n\r\n```cmd\r\ngit config --global alias.br branch\r\n```\r\n\r\n![alt tag](https://i.imgur.com/NIc71AO.jpg)\r\n\r\n```cmd\r\ngit config --global alias.ck checkout\r\n```\r\n\r\n```cmd\r\ngit config --global alias.sw switch\r\n```\r\n\r\n```cmd\r\ngit config --global alias.cm commit\r\n```\r\n\r\n```cmd\r\ngit config --global alias.lg \"log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)\u003c%an\u003e%Creset' --abbrev-commit --date=relative\"\r\n```\r\n\r\n將前面這一大串變成一個別名，這樣以後只需要執行 `git lg` 即可，\r\n\r\n![alt tag](https://i.imgur.com/IvQLsMR.png)\r\n\r\n可能有人會問，那這個設定檔文件在哪裡呢?\r\n\r\n通常會在你的使用者底下，例如我這台電腦使用者為 HJ，設定檔文件就會在 **C:\\Users\\HJ** 底下，\r\n\r\n他是一個  **隱藏文件.gitconfig** ，打開他的話格式如下。\r\n\r\n![alt tag](https://i.imgur.com/iXjIqv9.jpg)\r\n\r\n不知道大家有沒有注意到 `--global` 這個參數，他代表的意思是全域的，如果說你今天是執行\r\n\r\n```cmd\r\ngit config alias.stu status\r\n```\r\n\r\n代表只有在該目錄底下時才會有作用。\r\n\r\n那這個有什麼用呢？ 試想一種情境，假設你在特定的資料夾底下，想要使用特定的信箱去 push，而其他的資料夾，\r\n\r\n則一樣使用公司的信箱，這時候，就非常適合使用這種方法完成。\r\n\r\n更多資訊細節可使用以下命令查看\r\n\r\n```cmd\r\nman git-config\r\n```\r\n\r\n### git 更新\r\n\r\n```cmd\r\nsudo add-apt-repository ppa:git-core/ppa\r\nsudo apt-get update\r\nsudo apt-get install git\r\n```\r\n\r\n![alt tag](https://i.imgur.com/WrQNZln.png)\r\n\r\n## 使用 Git 一次 Push 到多個不同的遠端 ( remote )\r\n\r\n假如有一天 github 掛了，這樣是不是就不能 work 了，你可能會說本地端還有 ?\r\n\r\n但......多備份絕對是好事 !!  再這裡介紹如何一次 Push 到多個不同的遠端 ( remote )\r\n\r\n這裡用 [Bitbucket](https://bitbucket.org/product) 當作範例\r\n\r\n先使用下方指令查看\r\n\r\n```cmd\r\ngit remote -v\r\n```\r\n\r\n![alt tag](https://i.imgur.com/Qb5VHoP.png)\r\n\r\ngit remote 這個指令的更多說明可參考官方文件 [git-remote](https://git-scm.com/docs/git-remote)。\r\n\r\n接著我們使用下列指令新增一個 origin 的遠端\r\n\r\n```cmd\r\ngit remote set-url --add origin \u003curl\u003e\r\n```\r\n\r\n```cmd\r\ngit remote set-url --add origin git@github.com:twtrubiks/test2.git\r\n```\r\n\r\n![alt tag](https://i.imgur.com/FKzexVE.png)\r\n\r\n我們再用 git remote -v 查看一次，你會發現多了剛剛新增的遠端 ( remote )\r\n\r\n![alt tag](https://i.imgur.com/p1q7C4b.png)\r\n\r\n最後我們再 push\r\n\r\n![alt tag](https://i.imgur.com/6VKh8Bz.png)\r\n\r\n仔細看，是不是一次 push 到多個不同的遠端 ( remote )，非常方便!!\r\n\r\n***GitHub***\r\n\r\n![alt tag](https://i.imgur.com/JljPJHJ.png)\r\n\r\n***Bitbucket***\r\n\r\n![alt tag](https://i.imgur.com/rkYHNl4.png)\r\n\r\nP.S 設定檔在資料夾底下的隱藏檔 \".git\" 底下，裡面有一個 config\r\n\r\n![alt tag](https://i.imgur.com/41xb8eu.png)\r\n\r\n補充幾個 git remote 的指令，他也支援 rename 以及 remove ，\r\n\r\n現在的 remote 如下，\r\n\r\n![alt tag](https://i.imgur.com/rr9SE3g.png)\r\n\r\n讓我們重新命名 remote，語法如下，\r\n\r\n```text\r\ngit remote rename \u003cold\u003e \u003cnew\u003e\r\n```\r\n\r\n```text\r\ngit remote rename origin2 origin\r\n```\r\n\r\n執行後，你會發現 remote 成功被修改成 origin 了，\r\n\r\n![alt tag](https://i.imgur.com/ixP1H7Z.png)\r\n\r\n接下來我們試試 remove，語法如下，\r\n\r\n```text\r\ngit remote remove \u003cname\u003e\r\n```\r\n\r\n```text\r\ngit remote remove origin\r\n```\r\n\r\n成功刪除，現在 remote 是空的了，\r\n\r\n![alt tag](https://i.imgur.com/OQFRWDg.png)\r\n\r\n接下來我們嘗試新增一個 remote，指令如下，\r\n\r\n```text\r\ngit remote add [-t \u003cbranch\u003e] [-m \u003cmaster\u003e] [-f] [--[no-]tags] [--mirror=\u003cfetch|push\u003e] \u003cname\u003e \u003curl\u003e\r\n```\r\n\r\n```cmd\r\ngit remote add origin git@github.com:blue-rubiks/t11.git\r\n```\r\n\r\n![alt tag](https://i.imgur.com/cKsiBBs.png)\r\n\r\n如果我們想修改 origin 的 url，可以使用\r\n\r\n```cmd\r\ngit remote set-url origin git@blue.github.com:blue-rubiks/t11.git\r\n```\r\n\r\n![alt tag](https://i.imgur.com/LJICTNM.png)\r\n\r\n## Multiple SSH Keys settings for different github account\r\n\r\n* [Youtube Tutorial - Multiple SSH Keys settings for different github account](https://youtu.be/gDxG-4tF7B8)\r\n\r\n[Multiple SSH Keys settings for different github account](https://github.com/twtrubiks/Git-Tutorials/blob/master/Multiple_SSH_Keys_settings.md)\r\n\r\n## Git-Flow  基本教學以及概念\r\n\r\n* [Git-Flow Tutorials - youtube](https://youtu.be/zXlta66thZY)\r\n\r\n* [Git-Flow SmartGit Tutorials - youtube](https://youtu.be/ualXHytifbg)\r\n\r\n[Git-Flow  基本教學以及概念](https://github.com/twtrubiks/Git-Tutorials/tree/master/Git-Flow)\r\n\r\n## PR (Pull Request) 教學\r\n\r\n* [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)\r\n\r\n* [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)\r\n\r\n[PR (Pull Request) 教學](https://github.com/twtrubiks/Git-Tutorials/tree/master/pr-tutorial)\r\n\r\n## Linux 注意事項\r\n\r\n* [Youtube Tutorial - Linux 教學 - git 乎略 file mode (chmod) 改變](https://youtu.be/QCh2k903Yak)\r\n\r\n這邊是和大家說一些同時在 windows 以及 linux 底下使用 git 可能會遇到的問題.\r\n\r\n首先, 在 linux 底下執行以下指令\r\n\r\n```cmd\r\nsudo chmod -R 777 folder\r\n```\r\n\r\ngit 會默認它為改變, 要怎麼把它忽略呢 ? 請執行以下指令 ,\r\n\r\n```cmd\r\ngit config core.fileMode false\r\n```\r\n\r\n也可參考這篇文章 [Git ignore file mode (chmod) changes](https://stackoverflow.com/questions/1580596/how-do-i-make-git-ignore-file-mode-chmod-changes)\r\n\r\n### 格式化\r\n\r\n`core.autocrlf`\r\n\r\nWindows 使用 Enter (Carriage Return 簡寫為 CR) 和 換行(Line Feed 簡寫為 LF) 這兩個字元來定義換行,\r\n\r\n而 Mac 和 Linux 只使用一個換行 (Line Feed 簡寫為 LF) 字元.\r\n\r\n所以會導致跨平台協作時出問題.\r\n\r\n在 windows 上可以這樣設定 ( 代表 LF 會被轉換成 CRLF)\r\n\r\n```cmd\r\ngit config --global core.autocrlf true\r\n```\r\n\r\nLinux 或 Mac 系統\r\n\r\n```cmd\r\ngit config --global core.autocrlf input\r\n```\r\n\r\n以上這樣設定, 會在 Windows 上保留 CRLF，而在 Mac 和 Linux 以及 repo 中保留 LF.\r\n\r\n如果你想更深入的了解, 可參考 [格式化-core.autocrlf](https://git-scm.com/book/zh-tw/v1/Git-客製化-Git-設定#格式化與空格).\r\n\r\n### 修改 editor\r\n\r\n```cmd\r\ngit config --global core.editor \"vim\"\r\n```\r\n\r\n## Reference\r\n\r\n* [13 Git tips for Git's 13th birthday](https://opensource.com/article/18/4/git-tips)\r\n\r\n## Donation\r\n\r\n文章都是我自己研究內化後原創，如果有幫助到您，也想鼓勵我的話，歡迎請我喝一杯咖啡:laughing:\r\n\r\n綠界科技ECPAY ( 不需註冊會員 )\r\n\r\n![alt tag](https://payment.ecpay.com.tw/Upload/QRCode/201906/QRCode_672351b8-5ab3-42dd-9c7c-c24c3e6a10a0.png)\r\n\r\n[贊助者付款](http://bit.ly/2F7Jrha)\r\n\r\n歐付寶 ( 需註冊會員 )\r\n\r\n![alt tag](https://i.imgur.com/LRct9xa.png)\r\n\r\n[贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8)\r\n\r\n## 贊助名單\r\n\r\n[贊助名單](https://github.com/twtrubiks/Thank-you-for-donate)\r\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fgit-tutorials","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwtrubiks%2Fgit-tutorials","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fgit-tutorials/lists"}