白老鼠2.0
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- tags: Git --- # Git 版本控管實戰:新手進階篇 ## Git 內部架構解析 ### 版本控管(Version Control) * 完整記錄軟體變化的過程(人、事、時、地、物) * 紀錄版本變化而𧗠生出 * 查詢歷史紀錄 * 復原變更 * 比對差異 * 標記版本 * 變更追蹤 * 多人版控進一步衍生出 * **協同作業** * 分支合併 * **版控流程** * 發行管理 ### 分散式版本控管(DVCS) * 優點 * 本地端的工作區會保有完整的儲存庫 * 每個人都擁有一分完整的儲存庫備分(Full Backup) => 分散式 * 不需要 Server 支援即可運作版控 => 不用網路連線,大幅節省開發時間 * 操作都在本地儲存庫中 => 快、離線的版本、完整的歷史紀錄 * 擁有強悍的**合併追蹤**能力 * 取得他人變更版本後,可透過合併方式進行整合 * 合併多人的版本只要有存取共用儲存庫的權限或管道即可 * 缺點 * 無法**鎖定**版控策略(僅能使用合併策略)==> **專注在自己的分支** * 無法針對專案進行**精細的權限控管** * 若要鎖定特定資料夾,可透過 Submodules 實現 (Owner 完整的 repo,其他人則在 Submodules 內進行) ### 工作區(Workspace / Work Tree) https://gitbook.tw/chapters/using-git/working-staging-and-repository.html * 頻繁異動的開發目錄 * 在工作區執行任意 git 命令 * 內含 .git 隱藏資料夾(本地儲存庫) * 沒有 .git 目錄 => `git init` * 刪掉 .git 目錄 => 脫離版控 * 擁有 .git 目錄 == 擁有所有原始碼 * 工作區底下檔案的狀態 * untracked * tracked * unmodified * modified * staged ### 儲存庫(Repository) * 本地儲存庫(Local Repository) * 預設位於 .git 資料夾 * 遠端儲存庫(Remote Repository) * 僅**儲存庫** (Bare Repository) * 實際上是將本地的 .git 資料夾上傳到遠端儲存庫 * 透過 `git clone --bare` 僅下載遠端儲存庫回來 * 共用儲存庫(Shared Repository) * `git init --bare` 建立共用儲存庫 * 並不是僅限於git Server,只要是能遠端同步的工具,都可以當作遠端儲存庫使用 * Dropbox、Google Drive ...etc * 再用 `git clone file:///path/to/repo.git/` checkout 即可 #### `--bare` * https://moelove.info/2016/12/04/Git-%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93%E5%92%8C%E8%A3%B8%E4%BB%93%E5%BA%93/ * 在預設的情況下,不管是 `git init` 或 `git clone` 都會一併建立[工作區(Workspace / Work Tree)](#%E5%B7%A5%E4%BD%9C%E5%8D%80%EF%BC%88Workspace--Work-Tree%EF%BC%89) * 包含 tags、local branches,但不包括 remote tracking branches * 若不須要 git workspace,搭配 `--bare` 參數即可乎略(不建立)git work tree * 若在沒有 work tree 的 repo 進行操作,則會出現訊息:`fatal: this operation must be run in a work tree` #### `--mirror` * https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---mirror * https://stackoverflow.com/questions/3959924/whats-the-difference-between-git-clone-mirror-and-git-clone-bare * 含蓋所有的 refs(即包含 tags、local branches、remote tracking branches) ### 了解 Git 資料結構 #### 物件(Object):immutable * [Git Internals - Git Objects](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) * 用來保存**儲存庫**中所有的**檔案**與**版本紀錄** * 類似當下狀態的 snapshot * 區分四種物件類型 * blob * 檔案內容 * `git hash-object <fileName>` => 根據檔案內容產生物件名稱 * commit => 提交 * author 和 committer 可以不一樣(ex. 重新 commit:rebase、`commit --amend`) * tree => 目錄 * 檔案名稱不變,內容改變 => 新的 tree * 檔案名稱改變,內容不變 => 新的 tree * tag * annotated tag 為物件型態(with `-a` property):以二進制檔案型式儲存 * lightweight tag 為參考型態(without `-a` property):以文字檔案型式儲存 (不會產生 tag 物件) * 可使用 [`git cat-file`](https://git-scm.com/docs/git-cat-file) 可以知道 git objects 的類型 * `git cat-file` 看到的 ID 為檔案**內容**所運算(SHA-1 hash)而來,若 git object 檔名相同(即 ID 相同),則代表檔案內容一致 * `-t` : type 類型 * `-p` : print 內容 * `-s` : size 檔案大小 * 以下圖為範例 ```shell= $ git cat-file -t a821ff3dfa531821967cc0380570bb01356c05a2 tree # 放置 git object 之資料夾名稱 = a8 # git object 檔案名稱(ID) = 21ff3dfa531821967cc0380570bb01356c05a2 # 使用 sha1 值前 2 碼當作目錄名稱,目的在於提升讀取效率 ``` ``` .git | ... | |- objects // 所有資料都在 objects 底下 | |- a8 | | 21ff3dfa531821967cc0380570bb01356c05a2 | | | |- f5 | | eea678d87a8664e4c76e12d3ef5c4ff775ad58 | | | |- fb | | 8821d9a4cd57a2e6c5e4ae6a6706bcb89f2ce7 | |- info | '- pack '- refs |- heads '- tags ``` * **頻繁變動的大檔案**不應 commit 到 Git (因為占空間) * 1g 的檔案,就會產生 1g 的 blob => 使用 [Git Large File Storage (LFS)](https://git-lfs.github.com/) 解決 (需 server 有支援) #### 索引(index) * 記錄有哪些檔案即將要被提交到下一個 commit 版本中 (保存要進儲存庫之前的**所有檔案狀態**) * `git add` 會將 tracked file 記錄到 git index file(git file status => Staged),並且建立一個 blob 物件([Blob 對象](http://www-cs-students.stanford.edu/~blynn/gitmagic/intl/zh_tw/ch08.html#_blob%E5%B0%8D%E8%B1%A1)) * working directory => index => object storage * ~~俗話說的好:有 `git add` 有保佑~~,有 `git add` 過都會有檔案的 blob,即使沒有 commit 指向它 * `git commit` 時,會建立 commit object file,並指向 git index file 中異動檔案的 blob 物件 * 屬於一種「可變的」(mutable) 檔案類型 * 主要位於 `.git/index` 檔案 * 包含了所有**版控中**的檔案(可利用 .gitignore 排除檔案受到 index 版控) * 空資料夾是無法加入版控的 (通常會用 `.gitkeep` 空檔使資料夾加入版控) * 工作區與索引的檔案為一對一Mapping * 索引有可能錯亂 XD (壞掉時將 index 砍掉重建就好了) ### Git 物件結構的優點 * 有效率的處理大型專案 * 以**檔案內容**換算 SHA1 Hash => 絕對不可能發產生衝突 * 歷史紀錄保護 * 每個版本包含上一個版本的 hash 值 * 檢查 git 儲存庫的完整性:`git fsck` * 定期的封裝物件 * 對於不常用的物件會自動進行壓縮處理 * `git gc` => 壓縮到 objects/pack 目錄下,同時會將不常用到的物件刪除 * 使用 `git gc --prune=now` 會立刻清除 Unreachable 物件 * 等價於 `git gc` + `git prune --expire=now` * ex: `git add .` 後又修改了同一份檔案後再次 `git add .` ==> 產生兩個 blob 物件,但 commit 僅參考到最後一份 blob 物件,第一份 blob 物件會在 `git gc` 時被清除 ### 儲存庫、工作目錄與索引的關係圖 ``` | | | | |------------------ git commit -a ------------------>| | | | | | |------- git add (-u) ------>|----- git commit ----->| | | | commit / tree / tag | | | | |-- git push -->| | | | | 工作目錄 索引 本地儲存庫 遠端儲存庫 | | | | |<---------------------------- git pull -----------------------------| | | | | | | |<- git fetch --| | | | | |<----------- git checkout <branch_name> ------------| | | | | | |<-- git checkout -- file ---| | | | | | | | |-- git diff --cached --| | | | | | |--------- git diff ---------| | | | | | | ``` ### git checkout 從索引內取得檔案 / 資料夾內容 #### `--` 請參考:[還原](#還原) * [git checkout -- . vs git checkout](https://stackoverflow.com/questions/41101998/git-checkout-vs-git-checkout) * [Why do we use double dash in “git checkout -- .”?](https://stackoverflow.com/questions/23208156/why-do-we-use-double-dash-in-git-checkout) ### Git Reset 應用技巧 * https://dotblogs.com.tw/wasichris/2016/04/29/225157 * 主要用途:將當前分支復原變更 * `--mixed` | `--soft` | `--hard` * `--mixed` [預設值] 工作目錄變更會保留,也就是保留 working directory 中的異動內容 (僅更新索引及指標位置) * `--soft` 工作區目錄變更會保留,同時也保留 index 中的異動內容 * `--hard` 工作區檔案內容未 commit 的會被直接還原到指定版本 * [`--soft vs. --hard`](https://stackoverflow.com/questions/3528245/whats-the-difference-between-git-reset-mixed-soft-and-hard) ![](https://i.stack.imgur.com/qRAte.jpg) |Reset 模式|所在位置(HEAD)|變更狀態紀錄(INDEX)|工作目錄|說明| |--|--|--|--|--| |soft|changed|unchanged|unchanged|僅移除 **commit** 變成新版未 commit,內容仍是新版的| |mixed|changed|changed|unchanged|index 移除 **staged** 標記,變成 **Modified** or **Untracked**,內容是新版的| |hard|changed|changed|changed|回到上一版版本,其間變更完全移除(接近 svn revert),內容及狀態皆是上一版| ![](https://trello-attachments.s3.amazonaws.com/583c75c90173173906e0b4ce/1023x654/ad3080ce58b146f8f0b5e343771a8a17/_output_gitlifecycle.png) * 取得檔案變更應使用 `git checkout` 而非濫用 `git reset` * `git reset -p`: 互動式選擇還原區塊 #### 復原最近一次重置 (reset)、合併 (merge) 或 重訂基底 (rebase) * `git reset --hard ORIG_HEAD`: 復原**重大變更**的前一版 #### `git reflog` * 利用 git commit object 在 `git gc` 之前都存在的特性,將因 reset 而沒有指向的 commit 調出來 * `reflog` 列出來的內容是做完指令的結果,所以要還原必須再往前找一版 #### `commit --amend` * 重新 commit 最近的 commit * 修改 commit 的 Git user name、email: * [`--reset-author`](https://git-scm.com/docs/git-commit#git-commit---reset-author) * [`--author=<author>`](https://git-scm.com/docs/git-commit#git-commit---authorltauthorgt):`git commit --amend --author="Author Name <email@address.com>" --no-edit` * https://help.github.com/articles/setting-your-commit-email-address-in-git/ * https://stackoverflow.com/questions/3042437/change-commit-author-at-one-specific-commit * https://stackoverflow.com/a/1320317/8530187 * https://yulun.me/2014/git-tips-change-author-and-email-in-previous-commits/ ## Git 分支合併技巧 ### 分支 (Branching) * 一個指向某個 commit id 的 pointer * 每一個 commit 是將 index 的內容寫入 respository(請參考索引一節) * branch、lightweight tag(非 `-a`)、HEAD 都是一種指向 commit 的**指標**(以文字檔案型式儲存著 commit id) * 分支圖是從 refs 中找出所有儲存指標的檔案,並將它們所指向的 commit 及其 parents 繪製出來 * HEAD 指向一個沒有 branch 指向的 commit => 稱為 detached HEAD * 分支刪除並不會保留任何紀錄 ### 孤兒分支(orphan) * `git checkout --orphan <branch_name>` * 沒有 parent 的 commit object * 一個 repo 中可以有多個 root commit * 範例: * GitHub Pages * [台北捷運](https://github.com/othree/taipei-mrt) * 設計師將寫好的版型放到 orphan 分支,前端工程師比對差異進行套版 ### 關於「分支」的真正意義 * 標記"時間維度"的指標 * 「分支 Branch」是一個會隨時間演進的指標,當下時間點的索引狀態 (開發用) * 任何一條分支的異動,都不會影響其他分支 * 「標籤 Tag」是一個不會隨時間演進的指標 (參考用) * 「切換分支 Checkout」等同於「控制時間維度」 ### 檢示所有分支詳細資訊 * https://git-scm.com/docs/git-branch#git-branch--v ### 分支更名(改名) * https://git-scm.com/docs/git-branch#git-branch---move * `git branch --move [<oldbranch>] <newbranch>` * 重新命名 `.git/refs/heads/<oldbranch>` 分支 ### 合併「不同世界」的分支 * `git merge --ff`(default)=> 快轉 (Fast Forward):快速合併,直接使用每一次異動的 commit,最好使用在同一分支的情況下,ex. 本地 merge 遠端 ``` C <-- develop C <-- develop, test | | B <-- test ==> B | | A A ``` * 只有異動 commit 的父 commit 才能使用 Fast Forward 至異動(同一條 branch) * `git merge --no-ff`=> No Fast Forward:將多個異動併為一個 commit * 兩條意義不同的分支合併應該使用此合併策略,使線圖清晰 * master 每一個版本都會是穩定版 ``` M | \ <-- develop | o <-- test | o M <-- develop | o | o <-- test ===> o | o o o | o o o | o / o / I I ``` * `git merge --ff` vs. `git merge --ff-only` * `--ff`:預設行為,若不行使用 Fast Forward 則用自動轉為 No Fast Forward * `--ff-only`:明確要求必須使用 Fast Forward,若不能使用 Fast Forward 時則拒絕操作 * `git pull` == `git fetch` + `git merge` * 所以遠端比本地新旳時候會無法 `git push` * `git pull --rebase` * https://ihower.tw/blog/archives/3843 * 加上 rebase 的意思是,會先 1. 把本地 repo 從上次 pull 之後的變更暫存起來 2. 回復到上次 pull 時的情況 3. 套用遠端的變更 4. 最後再套用剛暫存下來的本地變更 * [`git pull --rebase --autostash`](https://git-scm.com/docs/git-pull#git-pull---autostash) * 無法 Fast Forward 的情形,本地端與遠端版本已經不是同一條分支了 ``` O <-- origin/master M | <-- master o o o / I ``` * 解法一:`git cherry-pick <commit_id>` * 解法二:`git rebase` (等同於 reset 後再做 cherry-pick) ### 合併沒有共同祖先的分支 (即 orphan branch) * 透過 `git merge --allow-unrelated-histories <orphan_branch_name>` 合併孤兒分支 ### `--no-ff` vs. `--no-ff --no-commit` vs. `--squash` * [What are the differences between `--squash` and `--no-ff --no-commit`?](https://stackoverflow.com/questions/11983749/what-are-the-differences-between-squash-and-no-ff-no-commit) * `--no-commit`:套用合併內容而不直接 commit,待使用者自行 commit * 合併但不 commit (會同時 add to index) * 合併成功 != 建置成功,確定可正常建置再自行 commit * http://www.jianshu.com/p/58a166f24c81 * `--no-ff` 產生的 merge commit 上不會有 diff 資訊,要自行到被併入的 branch 中查看各個 commit #### `--no-ff` `(master) $ git merge --no-ff topic` ``` A -- B -- C topic A -- B -- C topic / ===> / \ D -- E -- F -- G master D -- E -- F -- G -- H master ``` #### `--no-ff --no-commit` ```bash (master) $ git merge --no-ff --no-commit topic Automatic merge went well; stopped before committing as requested (master) $ git status On branch master Your branch is up to date with 'origin/master'. All conflicts fixed but you are still merging. (use "git commit" to conclude merge) Changes to be committed: modified: README.md modified: package.json ... ``` ``` A -- B -- C topic A -- B -- C topic / ===> / \ D -- E -- F -- G master D -- E -- F -- G -- H master ``` #### `--squash` ```bash (master) $ git merge --squash topic Squash commit -- not updating HEAD Automatic merge went well; stopped before committing as requested (master) $ git status On branch master Your branch is up to date with 'origin/master'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: README.md modified: package.json ... ``` ``` A -- B -- C topic A -- B -- C topic / ===> / D -- E -- F -- G master D -- E -- F -- G -- H master ``` * 壓縮合併,但不產生線圖 * 等價於 cherry-pick & rebase -i squash ### 合併策略 * https://git-scm.com/docs/git-merge#_merge_strategies * https://git-scm.com/docs/git-rebase#_merge_strategies * https://git-scm.com/docs/merge-strategies * https://www.jianshu.com/p/58a166f24c81 * https://www.cnblogs.com/chaoguo1234/p/5347623.html * `git [merge | rebase] [-X ours | theirs] <commit>` * `-X ours`:以目前 head 所在的 branch 為主 * `-X theirs`:以不是 head 所在的 branch 為主 * `git checkout [--ours | theirs]` * https://gitbook.tw/chapters/branch/fix-conflict.html * https://nitaym.github.io/ourstheirs/ * 衝突方向 * 目前 head 指向的分支內容:current change * 對方分支內容:incoming change * 例如: * `(dev)$ git merge master`:dev <- current(在 dev 上建立 merge commit 將 master 內容 merge 至 dev,會把 merge commit 的另一個 parent 指向 master) * `(dev)$ git rebase master`:master <- current(將 dev 上的 commit 一個一個重新 commit 到 master,head 指向 master 且會一個一個移動到後方重新 commit 的新 commit) ### 列出已合併/未合併的分支 * 已合併: `git branch --merged` * 刪除已合併的分支 `git branch --merged | egrep -v "(^\*|master|dev)" | xargs git branch -d` * egrep 排除 當前分支、master、dev * https://stackoverflow.com/questions/6127328/how-can-i-delete-all-git-branches-which-have-been-merged * 未合併: `git branch --no-merged` ## Rebase git rebase 的重新 commit 為: 1. 建立一個沒有名字的 branch 2. 在此 branch 提交 3. 將原 branch 指向此 branch https://blog.yorkxin.org/2011/07/29/git-rebase ``` A -- B -- C topic A' -- B' -- C' topic / ===> / D -- E -- F -- G master D -- E -- F -- G master ``` * 在 topic 下指令 `git rebase master` * 移花接木,將 master 的 commit 接在 topic 的 commit之前(**不影響 master 之線圖**) 1. 先 `checkout` 到指定 commit 2. 後 `cherry-pick` 到一個沒有 branch 指向的 commit 3. 再 `checkout` 回自己 commit 後 `reset` 回沒有名字的 branch * rebase 的目的在於避免 merge 衝突 * 遠端有更新時,應當立刻 rebase!! * 通常有遠端的時候才會需要用到 * [`git rebase --autostash`](https://git-scm.com/docs/git-rebase#git-rebase---autostash) ### `git rebase --skip` * https://git-scm.com/docs/git-rebase#git-rebase---skip * https://stackoverflow.com/questions/9539067/what-exactly-does-git-rebase-skip-do * 跳過(略過)目前衝突的 commit,會放棄此 commit 的異動 ### `git rebase -i` * `git rebase -i <base_commit_id>`(可修改 base commit 後的 commit) * 參數 i 代表:Interactive Mode 互動模式 * pick = 要這條 commit ,什麼都不改 * reword = 要這條 commit ,但要改 commit message * edit = 要這條 commit,但要改 commit 的內容 * 修改完 commit 內容後可以再繼續新增其他 commit (於既有歷史紀錄中插入新版本) * squash = 要這條 commit,但要跟前面那條合併,並保留這條的 messages * 於 commit 時加上 `--squash=<commit>` 標註要壓縮哪個版本 * `git rebase -i --autosquash` 會自動將修正版本接在 commit 版本之後並設為 squash * [git rebase --autosquash](https://git-scm.com/docs/git-rebase#git-rebase---autosquash) * fixup = squash + 只使用前面那條 commit 的 message ,捨棄這條 message * 於 commit 時加上 `--fixup=<commit>` 標註要修正哪個版本 (效果同 squash) * exec = 執行一條指令(如執行 git 指令) * drop = 不要這條 commit * 還可以調整 commits 的順序,直接剪剪貼貼,改行的順序就行了 * rebase 修訂衝突後,應使用 `git add <file>` 標註衝突已解決,並使用 `git rebase --conitnue` 完成 (**而不是增加額外的commit**) * 修改第一個初始 commit * `git rebase -i --root` * 最早可跳至初始 commit 後的狀態 * https://stackoverflow.com/questions/2119480/edit-the-root-commit-in-git * https://git-scm.com/docs/git-rebase#git-rebase---root ### `git rebase --onto` * https://blog.yorkxin.org/2011/07/29/git-rebase * https://git-scm.com/docs/git-rebase * 指定要 rebase 的 base commit * 可用於要 rebase 已 merge 的 branch,解決只用 rebase 可能會無法正確 rebase 的情形 * `git rebase --onto <new base-commit> <current base-commit> <target-branchName>` * `git rebase --onto <new base-commit> <current base-commit>`(rebase 目前的分支) ## `git cherry-pick` * `--no-commit`:先不 commit,可利用此參數來多次 cherry-pick 不同的 commit 內容 * 在真正 commit 之前,都是屬於 cherry-pick 的過程 * 過程中可以 `git cherry-pick --abort` 來中斷 cherry-pick * 可以跨 repository 挑選需要的 commit ### `git cherry-pick` 多個 commit * 多個 commit:`git cherry-pick commit1 commit2` * 多個連續 commit:`git cherry-pick preStartCommit..endCommit` 或 `git cherry-pick startCommit^..endCommit` * https://git-scm.com/docs/git-cherry-pick#git-cherry-pick-codegitcherry-pickmaintmasternextcode * https://stackoverflow.com/questions/1670970/how-to-cherry-pick-multiple-commits * https://www.jianshu.com/p/08c3f1804b36 * https://segmentfault.com/q/1010000010185984 * `preStartCommit..endCommit` 代表從 `preStartCommit`(不包含此 commit 之異動內容)到 `endCommit` 為止的所有 commit * `startCommit^..endCommit` 代表從 `startCommit`(包含此 commit 之異動內容)到 `endCommit` 為止的所有 commit * cherry-pick 整個分支:`git cherry-pick parentBranchName..targetBranchName` ### 反向撿櫻桃 (Revert) * `git revert <commit_id>` ## HEAD * https://git-scm.com/docs/git-rev-parse#_specifying_revisions * https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git * **HEAD^** 一個 ^ 表示為第一個分支上的前一版,一個 ^ 就代表切換一次分支,後面接的數字則代表往前一版的第幾個分支 * Note: Windows command 需打 `HEAD^^` (`^`為跳脫字元 [Escape Characters](http://www.robvanderwoude.com/escapechars.php)) * **HEAD~** 一個 ~ 表示為前一版,預設都是於左數來第一個分支上往上找,後面接的數字則代表往前幾版 * **HEAD~** vs. **HEAD^** ![](https://i.stack.imgur.com/pDAzG.png) ### detached HEAD * https://www.git-tower.com/learn/git/faq/detached-head-when-checkout-commit ### origin/HEAD * [Re: [問題] 請問 git中 origin/HEAD 指的是](https://www.ptt.cc/bbs/Linux/M.1406900317.A.D51.html) * [How does origin/HEAD get set?](https://stackoverflow.com/questions/8839958/how-does-origin-head-get-set) * origin/HEAD 代表遠端的 default branch,也就是你 clone(與一些其他操作)時預設會切換到的 branch。預設會指到 origin/master ### ORIG_HEAD https://gitbook.tw/chapters/branch/how-git-know-what-current-branch-is.html > 當你在做一些比較「危險」的操作(例如像 `merge`、`rebase` 或 `reset` 之類的),Git 就會把 HEAD 的狀態存放在這裡,讓你隨時可以跳回危險動作之前的狀態 ## 還原/清除工作目錄檔案 ### 還原 * `git reset` 全部 Staged -> Unstaged,可搭配 `hard`、`soft`、`mixed` 等參數 * `git reset [--] <file>` Staged -> Unstaged `--`:代表將 `--` 後方的每個參數皆一率視為檔案路徑 * https://git-scm.com/docs/git-reset#git-reset-Resetasinglefileintheindex * https://stackoverflow.com/questions/7147270/hard-reset-of-a-single-file * https://stackoverflow.com/questions/6561142/difference-between-git-checkout-filename-and-git-checkout-filename/6561160#6561160 * `git reset <commitCode>` 會 checkout 至指定 commit,並將差異還原成 Unstaged 一樣可搭配 hard、soft、mixed 等參數 但要小心每一個參數處理差異的方式並不同 * `git checkout <file>` Staged 中有記錄:以 Staged 來還原 Staged 中沒有記錄:以目前 commit 來還原,Unstaged 內容會消失 ### 清除 * `git clean` 刪除 untracked 的檔案 * `git clean -n` 只列出將會清除的清單(Dry run, 不搭配其他參數則列出 non-ignored 的 untracked 檔案) * `git clean -f` 執行清除檔案(不搭配其他參數則清除 non-ignored 的 untracked 檔案) * 參數 n、f 可以搭配以下參數: * `git clean -X` 要清除 untracked 檔案(ignored files) * `git clean -x` 要清除所有 untracked 檔案(ignored and non-ignored files) * `git clean -d` 要同時清除 untracked 目錄 * 可指定路徑 ## 比對差異 * https://git-scm.com/docs/git-diff ### 不同的 git diff 方法 * 工作目錄(Work Tree) vs 更新索引(Index): `git diff` * 工作目錄(Work Tree) vs 最新版本(HEAD): `git diff HEAD` * 工作目錄(Work Tree) vs 歷史版本(Commits): `git diff <commit>` * 更新索引(Index) vs 最新版本(HEAD): `git diff --cached <HEAD>` (HEAD 可省略) * 更新索引(Index) vs 歷史版本(Commits): `git diff --cached <commit>` * 歷史版本(Commits) vs 歷史版本(Commits): `git diff <src-commit> <target-commit>` ### 比對二進位檔案之間的差異 * `git diff --binary` * 用於產生 patch ### 比對兩個版本之間的檔案異動清單與狀態 * `git diff --name-only`: 僅列出工作目錄與索引間異動的檔案 * `git diff --name-status`: 列出工作目錄與索引間異動的檔案及狀態 * `git diff --name-status <src-commit> <target-commit>`: 列出兩版本間異動的檔案及狀態 * 搭配 `--diff-filter` 指定特定狀態,配合 shell script 自動修正佈署環境檔案 ### 使用 git diff 產生 patch 修補檔與套用修補檔的方法 https://juejin.im/post/5b5851976fb9a04f844ad0f4 1. 建立 patch: `git diff <src-commit> <target-commit> > my-patch.patch` * 若包含二進位檔案須加上 `--binary` 2. 套用 patch: `git apply my-patch.patch` * 套用前可先使用 `git apply --check` 檢查套用 patch 過程是否會發生衝突 #### 正式環境上版與退版 * 上版: `git apply <patch_file>` * 退版: `git apply --reverse <patch_file>` #### `format-patch` vs. `diff >` https://yodalee.blogspot.com/2017/03/git-patch.html > 常見的 diff 其實也就是git diff 生成的 patch,內容就是:這幾行刪掉,這幾行加上去,用 git diff > commit1.patch 就能輕鬆生成。git patch system 則是用 git format-patch 來產生,它提供比 diff 更豐富的資訊 ## git stash * git stash 也是一種分支(refs/stash) * [`git stash show`](https://git-scm.com/docs/git-stash#git-stash-showltstashgt) * `git stash pop <n>`:n 是離現在最近的第幾個 stash(0-base) ## git show * https://git-scm.com/docs/git-show * [`git show --patch`](https://git-scm.com/docs/git-show#git-show---patch) * [Generating patches with -p](https://git-scm.com/docs/git-show#_generating_patches_with_p) ## git add / checkout --patch https://zlargon.gitbooks.io/git-tutorial/content/advanced/add_checkout_part_of_file.html ## 檔案 / 資料夾大小寫更名 1. `git config --local core.ignorecase false` 將 git 設定為大小寫敏感 2. `git mv client/themeCore/components/core/productCard temp` 3. `git mv temp client/themeCore/components/core/ProductCard` ## Git 遠端儲存庫管理 * `git remote` * [`--verbose`](https://git-scm.com/docs/git-remote#git-remote---verbose) * `git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git` * Syncing a fork * http://fred-zone.blogspot.tw/2015/09/git-fork.html * https://gitbook.tw/chapters/github/pull-from-github.html * https://gitbook.tw/chapters/github/syncing-a-fork.html * https://www.peterdavehello.org/2014/02/update_forked_repository/ * `$ git pull upstream master` * 一般建議使用 `git pull --rebase` * 衝突時,先 `git rebase --abort` 再說 * `git push [<遠端倉庫名稱>] [<本地分支名稱>][:<遠端分支名稱>]` * `git push origin local-name` * `git push origin local-name:remote-name` * https://git-scm.com/docs/git-push#git-push-codegitpushorigincode * https://git-scm.com/docs/git-push#git-push-ltrefspecgt82308203 * [git push --force and how to deal with it](https://evilmartians.com/chronicles/git-push---force-and-how-to-deal-with-it) * [GIT: PUSHING TO A REMOTE BRANCH WITH A DIFFERENT NAME](https://penandpants.com/2013/02/07/git-pushing-to-a-remote-branch-with-a-different-name/) * [將本地分支 push 到遠端](https://git-scm.com/docs/git-push#_description) * https://help.github.com/articles/pushing-to-a-remote/ * `git push --force` * https://git-scm.com/docs/git-push#git-push---force * `git push --force-with-lease` * https://git-scm.com/docs/git-push#git-push---no-force-with-lease * https://blog.csdn.net/wpwalter/article/details/80371264 * `git branch -u <遠端倉庫名稱>[/<遠端分支名稱>] [<本地分支名稱>]` == `git branch --set-upstream-to=<遠端倉庫名稱>[/<遠端分支名稱>] [本地分支名稱]` * [追蹤遠端分支](https://git-scm.com/docs/git-branch#git-branch--ultupstreamgt) * https://zlargon.gitbooks.io/git-tutorial/content/remote/upstream.html * `git checkout -b [<本地分支名稱>] --track <遠端倉庫名稱>[/<遠端分支名稱>]` * 建立新的追蹤分支(tracking branch),為 `git checkout <分支名稱>` 的[預設行為之一](https://git-scm.com/docs/git-checkout#git-checkout-emgitcheckoutemltbranchgt)(如果有同名的遠端分支名稱) * `git push -u <遠端倉庫名稱> <本地分支名稱>` == `git push --set-upstream <遠端倉庫名稱> <本地分支名稱>` * `git branch -u <遠端倉庫名稱>[/<遠端分支名稱>] [<本地分支名稱>]` * 刪除遠端分支 * https://git-scm.com/book/en/v2/Git-Branching-Remote-Branches * https://git-scm.com/docs/git-push#git-push---delete * `git push origin --delete <branchName>` * 會同時刪掉遠端追蹤分支 * 其他人則透過 `git fetch --prune` 刪掉遠端分支 * 刪除本地之遠端追蹤(remote-tracking)分支 * `git branch -r` 列出遠端追蹤分支 * https://git-scm.com/docs/git-branch#git-branch---delete * https://git-scm.com/docs/git-branch#git-branch---remotes * `git branch --delete --remotes origin/<branchName>` ## 標記版本 * 指向特定 commit 版本 * 標籤建立後通常不會再異動 * https://git-scm.com/docs/git-tag ### 建立與刪除輕量標籤 (lightweight tag) * 輕量標籤: 就只是個指標,並不會建立任何物件 * 建立標籤: `git tag <tag_name> <commit>` * 顯示訊息時只會顯示該版本的 commit 內容 * 刪除標籤: `git tag -d <tag_name>` * 或手動刪除檔案 `.git/refs/tags/<tag_name>` ### 建立與刪除標示標籤 (annotated tag) * 建立標籤: `git tag -a <tag_name> <commit>` * 需輸入版本訊息 (使用 `tag -n` 顯示 tag 訊息) * 通常用於 release * 刪除標籤: `git tag -d <tag_name>` ### 遠端標籤 * 推送遠端標籤: `git push --tags` * 刪除遠端標籤: `git push --delete origin <tag_name>` * 取得遠端標籤: `git fetch` * 若遠端標籤已不存在,要同時刪除本地標籤: `git fetch --prune-tags` ### 取出特定標籤的完整原始碼 * `git checkout <tag_name>`: 實際上是把標籤指向的版本取出 (因為標籤不是分支,所以會跑去 detached HEAD) * 因此應加上 `-b` 同時建立分支, ex `git checout -b hotfix/6.0.1 6.0.1` ## Submodule https://blog.chh.tw/posts/git-submodule/ ## Git 協同作業實戰 ==> 好的分支合併流程!! 1. 集中式版控流程 * 共用儲存庫 * 類似 SVN 2. 整合式管理版控流程 * blessed repository 對每個人都是唯讀 * devloper 將修改 pull request 給 integration manager,通過後再合併 * 類似 GitHub fork * pull 都從 blessed repository,push 則 push 自己的 * 透過 `git remote set-url --push origin <URL_TO_MY_REPO>` 設定 3. 獨裁者與副手工作流程 * 類似前一步,只是再加上副手幫忙 code review ## 認識 GitHub Flow * https://guides.github.com/introduction/flow/ * http://calvert.logdown.com/posts/2014/09/21/understanding-the-github-flow * http://blog.krdai.info/post/17485259496/github-flow ## Git Commit Message * http://karma-runner.github.io/5.0/dev/git-commit-msg.html * http://blog.fourdesire.com/2018/07/03/%E6%92%B0%E5%AF%AB%E6%9C%89%E6%95%88%E7%9A%84-git-commit-message/ * https://juffalow.com/other/write-good-git-commit-message * https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/ ## 其他 * 命令列呈現線圖 `git log --oneline --graph -n` * `--oneline`: 僅顯示第一行訊息,即 commit log * `--graph`: 以線圖呈現 * `-n`: 最近幾版 * 實用的 git alias ``` git config --global alias.co checkout git config --global alias.ci commit git config --global alias.st status git config --global alias.sts "status -s" git config --global alias.br branch git config --global alias.re remote git config --global alias.di diff git config --global alias.type "cat-file -t" git config --global alias.dump "cat-file -p" git config --global alias.lo "log --oneline" git config --global alias.ll "log --pretty=format:'%h %ad | %s%d [%Cgreen%an%Creset]' --graph --date=short" git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset %ad |%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset [%Cgreen%an%Creset]' --abbrev-commit --date=short" # Windows 限定 (因為要 TortoiseGit) git config --global alias.tlog "!start 'C:\PROGRA~1\TortoiseGit\bin\TortoiseGitProc.exe' /command:log /path:." ``` --- ## Git 工具 * [必要安裝] [Git 命令列工具](https://git-scm.com/) * [推薦安裝] [TortoiseGit](https://tortoisegit.org/) ( Windows only ) * [推薦安裝] [SourceTree](https://www.sourcetreeapp.com/) * [Visual Studio Code](https://code.visualstudio.com/) * [GitHub Desktop](https://desktop.github.com/) * [Fork](https://git-fork.com/) ( Mac only ) * [SmartGit](http://www.syntevo.com/smartgit/) * [GitExtensions](https://gitextensions.github.io/) ( Windows only ) * [tig](https://jonas.github.io/tig/) (Command Line) * [GitKraken](https://www.gitkraken.com ) ( Commerical ) * [Tower](https://www.git-tower.com/) ( Commerical ) ### 環境建立 * [Git 版本控管實戰:新手進階篇 - 範例版本庫 (Windows)]( https://gist.github.com/doggy8088/491d9da6b2452437f4af487137dd0340) * [Start-Sleep](https://ss64.com/ps/start-sleep.html) 必須使用 PowerShell 才能使用 * [Git 版本控管實戰:新手進階篇 - 範例版本庫 (macOS)]( https://gist.github.com/doggy8088/e535363a8f5c8ef3aeb31a1d234d4937) ### Git 教學影片(課後問卷) * [認識 Git 資料結構中的物件資料庫與物件之間的關係](https://www.youtube.com/watch?v=PZbSRy_ow0U) * [認識 Git 資料結構中的索引與檔案狀態的變化關係](https://www.youtube.com/watch?v=5c_7v0cIFk4) * [如何在 GitHub 使用 Fork / Pull Request 功能 (以 VS2013 為例)](https://www.youtube.com/watch?v=NTLAfy6lcdQ) * [透過 Visual Studio 2013 匯入方案到 Git 儲存庫的正確做法 ](https://www.youtube.com/watch?v=97BVjQyK8ag)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully