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
登入 github.com
account settings
SSH and GPG keys
New SSH key
填上任意 title,貼上剛 generate 出來的 public key
Add SSH key
Add a New Repo on GitHub
先有本地 repo 後有遠程 repo 時有用
github.com 登入後首頁
create a new repo
輸入 title
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 .
Untracked Files
實測切換分支 untracked files 也不會不見,可以在 local 先改好再決定要 add 到哪一個分支
Tutorial
Tutorial by Atlassian who owns Bitbucket