Git and GitHub

安裝

Linux 檢查有沒有 git:

$ git

Debian/Ubuntu Linux 安裝 git:

$ sudo apt-get install git

其它 Linux 版本安裝 git:官網下載,解壓縮

$ ./config
$ make
$ sudo make install

Windows 安裝 git:http://msysgit.github.io/ 下載安裝,之後有 issue 去這裡找 C:/Program Files/Git/ReleaseNotes.html

本地初始設置

$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

讓 git 接管一個目錄

$ mkdir learngit
$ cd learngit
$ pwd
$ git init
  • git init 之後會出現 .git 目錄,沒事不要手動亂改

  • 二進制格式檔案如圖片,視頻,docx 等沒辦法用版本控制系統

  • 文本編碼建議用 UTF-8,支持所有語言且所有平台皆有

  • 用 notepad++(https://notepad-plus-plus.org/ )取代筆記本,默認編碼設置為 UTF-8 without BOM。Windows notepad 的 UTF-8 編碼儲存總在檔頭加上 0xefbbbf

  • Unix 哲學:沒有消息就是好消息

時光穿梭

$ git add readme.txt
$ git commit -m "wrote a readme file"
$ git status

可以 add 很多個文件然後一次 commit 全部

$ git commit -m "add 3 files"

查看目前文件和當前分支裡的不同

$ git diff readme.txt

查看 commit 歷史紀錄。印出來的 16 進制大數是 commit id

$ git log
$ git log --pretty=oneline

改變當前版本指標 HEAD。HEAD^ 是上一版,HEAD^^ 是上上版,HEAD~100 是往上一百個版本。也可以直接指定版本的 commit id 的前幾位。

$ git reset --hard HEAD^
$ git reset --hard HEAD^^
$ git reset --hard 3628164

倒回上一版之後再 $ git log 就看不到最新版了,要倒回去需要查 commit id,只能用 reflog 查看命令歷史

$ git reflog

工作區和暫存區

  • working directory: 電腦裡能看到的目錄

  • repository: 版本庫,.git 目錄,包含

    • stage (index): 暫存區

    • master: 主分支

    • HEAD: 指向 master 的指標

  • git add 把 working dir 的內容存在 stage,

  • git commit 把 stage 的內容存進當前分支

撤消修改

撤消 working dir 裡的所有修改,倒回上一次 commit 或 add 的狀態(如果沒有 – 會變成創建新的分支)

$ git checkout -- readme.txt

撤消暫存區裡的修改

$ git reset HEAD readme.txt

刪除文件

用 rm 刪除 working dir 中某文件後:或是真正想將文件從分支刪除

$ git rm test.txt
$ git commit -m "remove test.txt"

若是誤刪,可救回來

$ git checkout -- test.txt

遠程倉庫

  • GitHub 和本地 repo 的聯繫是透過 ssh 加密的,所以如果電腦裡還沒有 ssh key 就需要先產生一組。

  • 有了 ssh key,GitHub 就能確定提交是本人推送的。若有多台電腦,各自產生 key 再逐一加到 GitHub 裡。

GitHub 初始設置

產生 ssh key

$ ssh-keygen -t rsa -C "youremail@example.com"

之後一路 Enter。設定完成會在 C:\Users\yuan.ssh 中出現私鑰 id_rsa 和公鑰 id_rsa.pub

把自己的 key 告訴 GitHub

  1. 登入 github.com

  2. account settings

  3. SSH and GPG keys

  4. New SSH key

  5. 填上任意 title,貼上剛 generate 出來的 public key

  6. Add SSH key

Add a New Repo on GitHub

先有本地 repo 後有遠程 repo 時有用

  1. github.com 登入後首頁

  2. create a new repo

  3. 輸入 title

  4. create repo

建立本地與遠端 repo 之間的關聯:

在本地鍵入

$ cd /c/Users/yuan/Desktop/learngit
$ git remote add origin https://github.com/beginnerSC/learngit.git

(origin 是 Git 默認遠程 repo 的名字)

將本地庫 repo 的 master 分支推送到遠程庫 master,需要 GitHub ID 密碼:

$ git push -u origin master

(參數 -u 用來建立本地 master 與遠端 master 的關聯,若非第一次 push 可省略)

從 GitHub 上 clone

先有遠程 repo 時有用

在 GitHub 上建立一個新 repo with README, named gitskills

$ cd /c/Users/yuan/Desktop/learngit
$ mkdir gitskills
$ git clone git@github.com:beginnerSC/gitskills.git

上面的 git@github.com:beginnerSC/.git 和 https://github.com:beginnerSC/.git 可互換。Git 支持多種協議,但通過 ssh 支持的原生 git 協議最快。

只開放 http 端口的公司內部無法使用 ssh 協議而只能用 https。

分支管理

創建與合併分支

  • HEAD 用來指向當前分支,只有一個分支 master 時 HEAD 指向 master 指標,master 指向分支內容

  • 創建新的分支如 dev 時會產生 dev 指標用來指向新分支內容,隨著 commit 更新。HEAD 改指向 dev,master 則指向原本的內容不動。

  • 合併時 Git 直接把 master 指向 dev 所指的內容

  • 合併完成後可以刪除 dev 分支(刪除指標),就只剩 master 分支

創建並切換到 dev 分支:

$ git branch dev
$ git checkout dev

$ git checkout -b dev

查看當前分支:

$ git branch

經過修改,add,commit 之後切回 master 分支:

$ git checkout master

在 master 分支看剛才修改的內容都不見了!因為 checkout 把 HEAD 指回 master 指標。

合併 dev 修改內容到 master 裡(要在 master 分支中執行):

$ git merge dev

合併之後所有更動只在 master stage 裡,要再 commit 和 push。

刪除 dev 分支:

$ git branch -d dev

合併時 git 提示這次合併是 Fast-forward,也就是直接改 master 指標,所以非常快。並非所有合併都能用 Fast-forward。刪除後用 git branch 查看分支,現在只剩下 master。

解決衝突

如果在兩分支 dev 和 master 中各自修改過同一個檔案,就可能產生衝突。Git 可以列出所有衝突:

$ git status

手動解決衝突:在 dev 分支中(?)修改,保存,然後 add,commit。 可以用 git log 看分支的合併情況:

$ git log --graph --pretty-oneline --abbrev-commit

解決衝突並合併之後刪除 dev 分支:

$ git branch -d dev

強制禁用 Fast-forward

情況允許時 Git 會自動用 Fast-forward 合併,但這種模式下刪除分支後會永久喪失分支信息。如果強制禁用,Git 就會創建一個新的 commit,分支信息就可以永久保存

在 master 分支中,強制禁用 Fast-forward 合併 dev:

$ git merge --no-ff -m "merge with no-ff" dev

因為這種合併要創建新的 commit,所以加上 -m 和 commit 描述。 查看歷史:

$ git log --graph --pretty=oneline --abbrev-commit

結果不同於 Fast-forward 的只改變指標。

分支策略

  • master 應該非常穩定,只用來發布新版本,不能幹活

  • 建立幹活分支 dev

  • 每個人都有自己的分支 scott, bob, …,階段工作完成時合併到 dev

  • 修 bug 時每個 bug 都(在自己的分支下)開一個分支,修復後合併並刪除

  • 加新 feature 時也是,每個 feature 開一個分支

暫存(stash)

用 git stash 可儲存目前的狀態,停下手邊工作去執行一個暫時的任務,待任務完成再回復到 stash 前的狀態。

以下例子在 dev 分支上用 git stash 暫存,然後修 bug(issue-101),修完再切回來:

$ git stash
$ git checkout master
$ git checkout -b issue-101

(… 修復 bug …)

$ git add readme.txt
$ git commit -m "fix bug 101"
$ git checkout master
$ git merge --no-ff -m "merged bug fix 101" issue-101
$ git checkout dev

任務完成也切回 dev 分支後,先查看有哪些暫存工作階段:

$ git stash list

有兩種方法可以回復:回復後不刪除的 apply 和回復並刪除的 pop。apply 後面要指定 stash ID。

$ git stash pop
$ git stash apply stash@{0}

刪除 stash(可在 apply 之後使用)

$ git stash drop stash@{0}

強行刪除分支

以下例子為開發新功能創建一新分支,但在合併前就改變開發方向,決定刪除該分支

$ git checkout -b feature-vulcan

(… 開發新功能 …)

$ git status
$ git add vulcan.py
$ git commit -m "add feature vulcan"

此時銷毀此分支會失敗,原因是分支尚未合併,刪除會永遠失去分支內容:

$ git branch -d feature-vulcan

若要強行刪除,改用

$ git branch -D feature-vulcan

多人協作

在 working dir 中查看遠程庫的信息:

$ git remote

顯示更詳細的信息:

$ git remote -v

得到

origin  https://github.com/beginnerSC/learngit.git (fetch)
origin  https://github.com/beginnerSC/learngit.git (push)

代表可抓取和推送的地址。如果沒有推送權限,就看不到 push 的地址。

推送分支

推送 master 或 dev 分支到遠程庫:

  • $ git push origin master

  • $ git push origin dev

並非所有分支都要推送到遠程:

  • 主分支 master 要時刻與遠程同步

  • 開發分支 dev 也要時刻與遠程同步

  • bug 分支用於本地修復 bug,不一定要推到遠程

  • feature 分支是否推送取決於開發團隊

解決衝突

加入開發團隊,先 clone 並在本地創建 dev 分支:

$ git clone git@github.com:beginnerSC/learngit.git
$ git branch
$ git checkout -b dev origin/dev

用 git branch 可以看出 git clone 只能 clone master 分支,所以才要用 git checkout 另外創建一個。

現在每個人都把修改往 dev 推送,很容易修改到同一個檔案而產生衝突。例如 A 做了

$ git commit -m "add /usr/bin/env"
$ git push origin dev

然後 B 做了

$ git commit -m "add coding: utf-8"
$ git push origin dev

而 A 和 B 正好修改過同一個檔案,B 的 push 就有可能失敗。 得用 git pull 到本地解決衝突,但必需先指定本地 dev 和遠程 origin/dev 的鏈接:

$ git branch --set-upstream dev origin/dev

有了鏈接才能 pull 成功:

$ git pull

接著手動解決衝突再重新推送:

$ git commit -m "merge & fix hello.py"
$ git push origin dev

總結

多人協作工作模式:

  • 用 git push origin branch-name 推送自己的修改

  • 若推送失敗就代表遠程分支比自己的本地分支新,需用 git pull 試圖合併

  • 若合併有衝突則解決衝突,並在本地提交

  • 解決掉衝突後再用 git push origin branch-name 推送就能成功

  • 如果 git pull 提示 “no tracking information” 就代表本地分支和遠程分支的鏈接關係沒有創建,用 git branch –set-upstream branch-name origin/branch-name。

標籤管理

標籤是版本庫在某個時間點的快照。標籤是指向某個 commit 的指標,像分支一樣,但分支可以移動,標籤不行。

為當前 commit 版本打上標籤(先切換到該分支):

$ git tag v1.0

查看所有標籤:

$ git tag

查看歷史 commit ID 然後為某個 commit 打標籤:

$ git log --pretty-oneline --abbrev-commit
$ git tag v0.9 6224937

帶有說明的標籤(-a 指定簽名,-m 指定說明文字):

$ git tag -a v0.1 -m "version 0.1 released" 3628164

查看標籤信息(如果有說明也會印出來):

$ git show v0.9

通過 -s 用私鑰簽名一個標籤,無法偽造(要先安裝 gpg):

$ git tag -s v0.2 -m "signed version 0.2 released" fec145a

刪除本地標籤:

$ git tag -d v0.1

推送標籤到遠端:

$ git push origin v1.0

一次推送全部尚未推送的本地標籤:

$ git push origin --tags

若要刪除已推送到遠端的標籤,要先從本地刪除,然後到遠端再刪一次:

$ git tag -d v0.9
$ git push origin :refs/tags/v0.9

.gitignore

  • 在 working dir 下建一個 .gitignore 檔可以用來記下所有中不想被追蹤的文件

  • 檔名一定要是 .gitignore。在 windows 下會提示必須輸入檔名,用 Notepad++ 建立內容再另存新檔

  • 可用來忽略系統自動生成的文件,中間文件,可執行檔等,或帶有敏感信息的文件

  • 不需要自己編寫,GitHub 已經準備好各種 .gitignore 檔可以組合使用:https://github.com/github/gitignore

  • 建好記得也把 .gitignore 也 add & commit

# example .gitignore file

# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build

# My configurations:
db.ini
deploy_key_rsa

其它

  • 在任意 GitHub 項目主頁按 Fork 就在自己的 GitHub 帳號下 clone 了一個 repo,可以再 clone 回本地玩

  • 在任意 GitHub 項目主頁按 Settings 最下面有 Delete this repo

  • 把 github 裡的 html/css 檔路徑貼上即自動生成網址。網頁內容隨 github 更新:https://rawgit.com/

  • 最終章搭建 Git server 跳過

讓 Git 顯示顏色:

$ git config --global color.ui true

配置別名

Examples:

$ git config --global alias.st status
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
$ git config --global alias.unstage 'reset HEAD'
$ git config --global alias.last 'log -1'
$ git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

使用:

$ git st
$ git ci -m "bala bala bala..."
$ git unstage test.py
$ git last

倒回之前某一次 commit

$ git revert --no-commit 0766c053..HEAD
$ git commit

git clone v. git pull

  • git pull 會 pull 所有分支,git clone 只 clone master

    • clone 下來之後如果想要有 dev 的歷史,要另外 git checkout dev

  • 像 sandbox-quant 一樣想在一個 repo 裡 clone 另一個 repo 進來(如 pyminimax)只能用 clone。git pull 會企圖合併兩個 repo 的歷史失敗然後報錯 fatal: refusing to merge unrelated histories

Revert All Local Changes

$ git checkout .

File History

  • 這裡說 git 自帶 gitk 但純 cli 可能沒有

$ gitk [filename]

Untracked Files

  • 實測切換分支 untracked files 也不會不見,可以在 local 先改好再決定要 add 到哪一個分支

Tutorial