Git

資訊系必備的版本管理工具

  • 基本介紹
  • 基礎操作
  • 進階技巧

大綱

基本介紹

what is Git

版本控制?

  • 不用每次修正都開一個新的檔案
  • 有雷隊友把 code 整個刪掉了
  • 大專案出 bug 了,想找出是什麼時候開始出了問題

為什麼需要版本控制?

Git 是版本控制系統

Git 還能做什麼?

Cloud

You

Cloud

Push

You

Repository

Cloud

Push

You

Repository

Cloud

You

Repository

Friend

Clone

Cloud

You

Repository

Friend

Push

Cloud

You

Repository

Friend

Pull

GitHub

You

關於「Git」和「Github」

Git

Git

關於「Git」和「Github」

基本概念

區域劃分

基本流程

  • 修改 / 新增檔案
  • 移到手推車 add
  • 打包進倉庫 commit

雲端合作

  • 把檔案推到雲端 push
  • 把檔案拉回本地 pull

誰說資訊系不用GUI?

終端機

終端機

Sublime Merge

  • 簡潔易懂
  • 指令操作
  • 功能齊全
  • 顯示分支樹
  • 免費

下載安裝

官網:https://www.sublimemerge.com

介面

操作

指令

Ctrl + p

Git 基礎

關於自己做事

基礎指令

初始設定

創建倉庫 / 環境設定

創建倉庫

  • repository(repo)
  • 資料夾 為單位
  • 讓 git 知道這些內容要被管理
  • 初始化 git

儲存位置

Sublime Merge

Sublime Merge

選單 > File > New Repository

環境設定

  • 記錄作者資訊
  • 調整記錄資訊
    • file mode
  • 調整環境設置
    • 編輯器

e.g. 作者資訊

儲存位置

  • Global
    • ~/.gitconfig
    • 影響所有 repo

  • Local
    • <repo>/.git/config
    • 目前所在 repo

儲存位置 - Global

儲存位置 - Local

Sublime Merge

Sublime Merge

創建 commit

放上卡車 / 檢查卡車 / 送進倉庫

放上卡車

git add

用途?

  • 掌控每個 commit 大小
  • 避免把某些資訊丟進倉庫
  • 為工作設定斷點

Sublime Merge

檢查卡車

內容資訊

Sublime Merge

modified

untracked

staged

紀錄「改變」

modified

staged

送進倉庫

git commit

Sublime Merge

commit message

成功!

Demo & Lab

  1. 新增一個 repo 
  2. 做好環境設定
  3. 新增檔案 a.txt 並放入卡車
  4. 把卡車送進倉庫

指令 ver.

git init / config / add / status / commit

Review

  • 新增倉庫 git init
  • 環境設定 git config
  • 放上卡車 git add
  • 檢查卡車 git status
  • 送進倉庫 git commit

創建倉庫

  • $ git init
  • $ git init <repo_name>

環境設定

  • $ git config [--global] user.name "<your_name>"
  • $ git config [--global] user.email "<your_email>"

放上卡車

  • $ git add a.txt
  • $ git add *.c
  • $ git add .
  • $ git add --all

檢查卡車

$ git status

modified

untracked

staged

送進倉庫

  • $ git commit
  • $ git commit -m "<message>"

組合技

  1. $ git status
  2. $ git commit

請養成 commit 前檢查 status 的好習慣

Demo

$ 指令 time

復原操作

git restore / rm

unstaged

  • 剛剛 git add 某些檔案
  • 發現這個東西還不能丟到倉庫
  • 從 staging area 移除

Sublime Merge

unmodified

  • 剛剛修改了一些檔案
  • 測試時發現整陀爛光光
  • 只能回復到上一個 commit 的狀態

Sublime Merge

Sublime Merge

Demo & Lab

Task: 創建並讓 b.txt 在 staged 跟 modified 間反覆橫跳移動

指令 ver.

git restore / rm

unstage

  • $ git restore --staged a.txt
  • $ git restore --staged *.c

第一個 staged

  • 新的 repo
  • 新增 a.txt 並放入 staging area
  • 從 staging area 移除

指令

哭阿

git restore

  • git restore 是以 commit 為基底進行復原
  • 沒有半個 commit 當然會出事情

怎麼辦

  • 下載 Sublime Merge
  • 重開一個 repo 
  • 不管了直接 commit
  • ...?

指令

指令

  • $ git rm --cached a.txt
  • $ git rm --cached *.c

git rm

  • 把檔案從 tracked 直接刪除
  • --cached:改成變為 untracked

unmodified

  • $ git restore a.txt

複習

  • $ git restore [--staged] <file>
  • $ git rm [--cached] <file>

太多好難記?

Demo

$ 指令 time

其他指令

操作歷史

查看歷史 / 刪除歷史 / 暫存桌面 / 回到過去

查看歷史

  • commit 順序
  • 作者與內容
  • 搜尋功能
    • 作者
    • 時間
    • 檔案

Sublime Merge

Sublime Merge

進階搜尋

刪除歷史

  • 不小心手殘把錯的東西 git add
  • 不小心手殘把錯的東西 git commit
  • 完蛋zzz

刪除歷史

  • 創造一個新的 commit 修改
  • git log 長很醜
  • 強迫症發作
  • 做事沒效率
  • 專案沒進度
  • 人生沒希望

Sublime Merge

刪除 commit

  • soft:改成 staged
  • mixed:改成 modified
  • hard:直接刪除

mixed 結果

WARNING !

git reset --hard 是一個十分危險的指令,一旦使用所有進度都會直接消失。請勿搭配酒精使用,並確認無誤再下達。

回到過去

  • 做了一堆 commit
  • 突然想回到過去看看以前的狀態

Sublime Merge

結果

桌面不乾淨

暫存桌面

  • 現在的檔案不足以 commit
  • 但又需要清空以放下其他東西
  • 暫時存檔之後取出

Sublime Merge

只會儲存 staged

  • stage all
  • stash

結果

取回暫存 - pop

回歸正題

  • 原本想要回到過去被卡住
  • stash 清空桌面
  • pop 之後取回

回到過去

結果

回到現在

修改歷史?

  • 先回到過去
  • 在歷史紀錄上做不同的修改
  • 甚至做出 commit...

結果

Detached HEAD

  • 目前 HEAD 所在的位置不屬於任何 branch
  • 離開後不容易找到這些 commits
  • 謹慎使用 
  • 先不要碰

Demo

把桌面清空 回到第一個 commit 再回來並回復桌面

Review

  • 查看歷史 git log
  • 刪除歷史 git reset
  • 暫存桌面 git stash
  • 回到過去 git checkout

查看歷史

$ git log

刪除歷史

  • $ git reset <commit_ID>
  • $ git reset --soft <commit_ID>
  • $ git reset --hard <commit_ID>

回到過去 - 未清空

暫存桌面

  • $ git add --all
  • $ git stash

只會暫存 staging area

取回暫存

$ git stash pop

回到過去

  • $ git checkout <commit_ID>

回到現在

  • $ git checkout master

git checkout

  • git checkout 其實更常用於切換分枝(branch)
  • 其中 master 就是分支名稱
  • 有時視情況會是 main / 其他名稱
  • 下半堂會更詳細介紹

Demo

$ 指令 time

隱藏資訊

.gitignore

隱藏資訊

  • 機密檔:secret_key.txt
  • 執行檔:a.exe a.out
  • 系統檔:.vscode/ .DS_Store
  • 暫存檔:a.txt~ b.txt.swp

小心再小心

  • 每次都不要 add 到那些檔案
  • 不能使用 git add --all
  • git status 很醜
  • 強迫症發作
  • ...

.gitignore

  • 名稱就叫 ".gitignore"
  • 告訴 git 哪些檔案 / 資料夾底下不要紀錄
  • 不會被 git status / add  看到

.gitignore

secret_key.txt

*.exe
*.out

.vscode/
.DS_Store

*~
*.swp

Sublime Merge

指令

$ vim .gitignore

結果

影響範圍

  • .gitignore 可以每個資料夾底下都放一個
  • 影響範圍會因為位置而有所不同
  • 若撰寫前該檔案已 tracked 則不受影響
  • 可透過 git rm --cached <file> 變回 untracked

Demo & Lab

Task: 撰寫 .gitignore 使 git 無法追蹤 secret_key.txt

Q & A

$ git checkout break_time !

Git 進階

關於分工合作

分工 = branch

合作 = merge 

A

B

版本      

  +修改   

     =版本

b

A

B

E

C

D

F

G

branch

merge

g \(\approx\) e + f

b

e

c

f

d

g

branch:分化出不同的分支完成不同的部分


merge:將不同分支的內容整合在一起

branch

some rules:

  • 一個 branch 指向一個 commit
  • 同個 commit 可以有很多分支,但 Git 同時只會關注(checkout)一個分支
  • 每次 commit ,當前的 branch 也會跟著往後移動

A

git checkout main









B

main

A

git checkout main
git commit -m "C"








B

C

main

A

git checkout main
git commit -m "C"
git commit -m "D"







B

C

main

D

A

git checkout main
git commit -m "C"
git commit -m "D"
git checkout <commit ID of B>






B

C

HEAD

main

D

A

git checkout main
git commit -m "C"
git commit -m "D"
git checkout <commit ID of B>
git checkout -b dev





B

C

dev

main

D

A

git checkout main
git commit -m "C"
git commit -m "D"
git checkout <commit ID of B>
git checkout -b dev
git commit -m "E"




B

C

dev

main

D

E

A

git checkout main
git commit -m "C"
git commit -m "D"
git checkout <commit ID of B>
git checkout -b dev
git commit -m "E"
git checkout main



B

C

dev

main

D

E

A

git checkout main
git commit -m "C"
git commit -m "D"
git checkout <commit ID of B>
git checkout -b dev
git commit -m "E"
git checkout main
git commit -m "F"


B

C

dev

main

D

E

F

A

git checkout main
git commit -m "C"
git commit -m "D"
git checkout <commit ID of B>
git checkout -b dev
git commit -m "E"
git checkout main
git commit -m "F"
git checkout dev

B

C

dev

main

D

E

F

A

git checkout main
git commit -m "C"
git commit -m "D"
git checkout <commit ID of B>
git checkout -b dev
git commit -m "E"
git checkout main
git commit -m "F"
git checkout dev
git commit -m "G"

B

C

dev

main

D

E

F

G

merge

some rules:

  • A 併進 B ≠ B 併進 A
  • A 併進 B:將 branch A 的資訊帶回 B
  • 「合併」就可能發生「衝突」(兩邊的意見不合)

main

main

dev

main

dev

main

dev

merge!

int stack[100], top = 0;
void push(int x){
}
int pop(int x){
}
int stack[100], top = 0;
void push(int x){
  stack[top++] = x;
}
int pop(){
}
int stack[100], top = 0;
void push(int x){
}
int pop(){
  return stack[--top];
}
int stack[100], top = 0;
void push(int x){
  stack[top++] = x;
}
int pop(){
  return stack[--top];
}

A

git checkout main

B

C

dev

main

D

E

F

D

F

A

git checkout main
git merge dev

B

C

dev

main

D

E

F

G

G

Merge this?

main

dev

main

dev

?

conflict!

conflict

  • 兩邊對相同的地方做不同的修改

  • Git 無法自動判斷哪邊該留下

  • 需要手動合併

int gcd(int a, int b){
<<<<<<< HEAD
    while(b != 0){
        int tmp = b;
        b = a % b;
        a = tmp;
    }
    return a;
=======
    return a % b == 0
         ? b
         : gcd(b, a % b);
>>>>>>> rec
}
int gcd(int a, int b){
}
int gcd(int a, int b){
    while(b != 0){
        int tmp = b;
        b = a % b;
        a = tmp;
    }
    return a;
}
int gcd(int a, int b){
    return a % b == 0
         ? b
         : gcd(b, a % b);
}
int gcd(int a, int b){
    return a % b == 0
         ? b
         : gcd(b, a % b);
}
<<<<<<< HEAD
合併前的檔案內容
=======
合併進來的分支的檔案內容
>>>>>>> 合併進來的分支名稱

Git 進階

關於異地同步

Sync strategy

push & pull

pull

push

「嘿,現在的版本樹長怎樣」

「嘿,我做了某某某修改」

git pull

for synchronization

A

B

dev

main

C

D

E

F

A

B

dev

main

C

D

E

A

B

dev

main

C

D

E

F

git fetch

A

B

dev

main

C

D

E

F

A

B

dev

main

C

D

E

A

B

origin/dev

origin/main

C

D

E

F

git fetch
git merge

A

B

dev

main

C

D

E

F

A

B

dev

main

C

D

E

A

B

origin/dev

origin/main

C

D

E

F

 = git pull

git pull

some rules:

  • (fetch)會將所有遠端的 commit 都抓下來
  • (merge)只會 merge 當前的 branch
  • 有 merge 就可能有 conflict 發生

A

B

dev

main

C

D

E

F

A

B

dev

main

C

D

E

G

origin/dev

A

B

main

dev

C

D

E

F

main

C

dev

G

A

B

origin/main

C

D

E

F

A

B

main

C

D

E

F

main

C

G

A

B

origin/main

C

D

E

F

H

dev

dev

origin/dev

git push

for contribution

A

B

dev

main

C

D

E

A

B

dev

main

C

D

E

F

A

B

dev

main

C

D

E

F

A

B

main

C

D

E

A

B

dev

main

C

D

E

F

git push origin dev

A

B

dev

main

C

D

E

F

git push <remote> <branch>

some rules:

  • 將本地的 branch 併進遠端的 branch
  • 只會動到指定的 branch
  • 必須是 fast-forward,否則會被 reject
  • remote 永遠是對的
git push origin dev

A

B

main

C

D

E

F

dev

A

B

main

dev

C

D

E

G

To github.com:username/reponame.git
 ! [rejected]        dev -> dev (fetch first)
error: failed to push some refs to 'github.com:username/reponame.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

E

F

origin/dev

G

dev

H

E

F

origin/dev

G

dev

E

F

dev

pull

merge

push

E

G

dev

E

F

G

dev

H

> git push origin dev
To github.com:username/reponame.git
 ! [rejected]        dev -> dev (fetch first)
error: failed to push some refs to 'github.com:username/reponame.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

> git pull
...
Auto-merging <filename>
CONFLICT (content): Merge conflict in <filename>
Automatic merge failed; fix conflicts and then commit the result.

> git add .

> git commit -m "merged"

> git push

Demo & Lab

Task:合作開發 1A2B 猜數字遊戲

background

  • 三個人一組

  • 透過 Git / GitHub 協作開發猜數字遊戲

For each group...

A

B

C

1.

創建 GitHub 帳號 & 新增 ssh key

github.com/signup

github.com/settings/ssh/new

A

B

C

2.

創建 repository

右上角 + 號

> New Repo

> Repo name 隨便取

> Create repo

A

B

C

3.

新增協作者

Repo 主頁

> Settings

> Collaborators

> Add people

A

B

C

local

ssh-keygen
cat ~/.ssh/id_rsa.pub

github

4.

把 repo clone 到本機上

github.com/username/repo-name

A

B

C

git@github.com:username/repo-name.git
cd /path/to/any/directory
git clone <repo_url>
cd repo-name

5.

A 先打個基本設定

A

B

C

*.exe
.gitignore
#include<stdio.h>
#include<stdlib.h>

int main()
{

}
main.c
git add .
git commit -m "template"
git push origin main

5

main

6.

BC 把 A 的修改 pull 下來

A

B

C

git pull

5

main

7.

B 強迫症發作,改了 A 的 coding style

A

B

C

#include<stdio.h>
#include<stdlib.h>

int main(){

}
main.c
git add .
git commit -m "coding style!!"
git push origin main

5

main

7

main

8.

C 寫了一些遊戲基本流程

A

B

C

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

#define len 4

void generate(char *s){
    // TODO
}

void input(char *s){
    // TODO
}

void response(char *s, char *a){
    // TODO
}

int main()
{
    char ans[10000], str[10000];
    generate(ans);

    while(true){
        input(str);
        response(str, ans);
    }
    printf("You won!\n");
}
main.c
git add .
git commit -m "game flow"
git push origin main

5

7

main

To github.com:username/repo-name.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'github.com:username/repo-name.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
void response(char *s, char *a){
    // TODO
}

<<<<<<< HEAD
int main()
{
    char ans[10000], str[10000];
    generate(ans);
=======
int main(){
>>>>>>> <commit ID>

    while(true){
        input(str);
        response(str, ans);
git pull
void response(char *s, char *a){
    // TODO
}

int main(){
    char ans[10000], str[10000];
    generate(ans);

    while(true){
        input(str);
        response(str, ans);
git add .
git commit -m "merge!"
git push origin main

C

5

7

origin/main

8

main

8

5

7

origin/main

8

main

5

7

main

5

7

8

8

main

pull

merge

push

5

8

main

9.

分工:

A 維護 main

B 處理 input

C 處理 output

A

B

C

5

7

8

8

main

input

output

git pull
git checkout -b "input"

B

C

git pull
git checkout -b "output"

10.

A 簡單加上了分隔線

A

B

C

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

#define len 4

void generate(char *s){
    // TODO
}

void input(char *s){
    // TODO
}

void response(char *s, char *a){
    // TODO
}

int main(){
    char ans[10000], str[10000];
    generate(ans);

    while(true){
        printf("==========\n");
        input(str);
        response(str, ans);
    }
    printf("You won!\n");
}
main.c
git add .
git commit -m "add separator"
git push origin main

5

7

8

8

main

input

output

10

main

11.

B 完成了數字生成和使用者輸入

A

B

C

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>
#include<string.h>

#define len 4

bool valid(char *s){
    if(strlen(s) != len) return false;
    for(int i = 0; i < len; i++){
        for(int j = 0; j < len; j++){
            if(i != j && s[i] == s[j]){
                return false;
            }
        }
    }
    return true;
}

void generate(char *s){
    printf("Set the answer (0 for auto generation): ");
    scanf("%s", s);
    if(s[0] == '0' && strlen(s) == 1){
        srand(time(0));
        while(true){
            for(int i = 0; i < len; i++){
                s[i] = '0' + rand() % 10;
            }
            s[len] = '\0';
            if(valid(s)){
                break;
            }
        }
    }else{
        if(!valid(s)){
            printf("Invalid input\n");
            generate(s);
        }else{
            printf("\033[1A\x1b[2K");
        }
    }
}

void input(char *s){
    printf("Guess a 4-digit number: ");
    scanf("%s", s);
}

void response(char *s, char *a){
    // TODO
}

int main(){
    char ans[10000], str[10000];
    generate(ans);

    while(true){
        input(str);
        if(!valid(str)){
            printf("Invalid input\n");
        }else{
            response(str, ans);
        }
    }
    printf("You won!\n");
}
main.c
git add .
git commit -m "finish input"
git push origin input

5

7

8

8

input

output

10

main

11

input

12.

C 完成回覆函數和勝利判斷

A

B

C

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<string.h>

#define len 4

void generate(char *s){
    // TODO
}

void input(char *s){
    // TODO
}

bool win(char *s, char *a){
    return strcmp(s, a) == 0;
}

void response(char *s, char *a){
    int cnt_a = 0, cnt_b = 0;
    for(int i = 0; i < len; i++){
        if(s[i] == a[i]){
            cnt_a++;
        }else{
            for(int j = 0; j < len; j++){
                if(s[i] == a[j]){
                    cnt_b++;
                    break;
                }
            }
        }
    }
    if(cnt_a) printf("%dA", cnt_a);
    if(cnt_b) printf("%dB", cnt_b);
    printf("\n");
}

int main(){
    char ans[10000], str[10000];
    generate(ans);

    while(true){
        input(str);
        if(win(str, ans)){
            break;
        }else{
            response(str, ans);
        }
    }
    printf("You won!\n");
}
main.c
git add .
git commit -m "finish output"
git push origin output

5

7

8

8

10

main

11

input

12

output

output

13.

身為 main branch 的維護者, A 要負責將 feature branches 合併進 main

A

B

C

git pull
git merge origin/input

5

7

8

8

10

main

11

input

12

output

13

main

14.

身為 main branch 的維護者, A 要負責將 feature branches 合併進 main

A

B

C

git merge origin/output

5

7

8

8

10

11

input

12

output

13

main

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
<<<<<<< HEAD
#include<time.h>
=======
>>>>>>> output
#include<string.h>

#define len 4

bool valid(char *s){
    if(strlen(s) != len) return false;
    for(int i = 0; i < len; i++){
        for(int j = 0; j < len; j++){
            if(i != j && s[i] == s[j]){
                return false;
            }
        }
    }
    return true;
}

void generate(char *s){
    printf("Set the answer (0 for auto generation): ");
    scanf("%s", s);
    if(s[0] == '0' && strlen(s) == 1){
        srand(time(0));
        while(true){
            for(int i = 0; i < len; i++){
                s[i] = '0' + rand() % 10;
            }
            s[len] = '\0';
            if(valid(s)){
                break;
            }
        }
    }else{
        if(!valid(s)){
            printf("Invalid input\n");
            generate(s);
        }else{
            printf("\033[1A\x1b[2K");
        }
    }
}

void input(char *s){
    printf("Guess a 4-digit number: ");
    scanf("%s", s);
}

bool win(char *s, char *a){
    return strcmp(s, a) == 0;
}

void response(char *s, char *a){
    int cnt_a = 0, cnt_b = 0;
    for(int i = 0; i < len; i++){
        if(s[i] == a[i]){
            cnt_a++;
        }else{
            for(int j = 0; j < len; j++){
                if(s[i] == a[j]){
                    cnt_b++;
                    break;
                }
            }
        }
    }
    if(cnt_a) printf("%dA", cnt_a);
    if(cnt_b) printf("%dB", cnt_b);
    printf("\n");
}

int main(){
    char ans[10000], str[10000];
    generate(ans);

    while(true){
        printf("==========\n");
        input(str);
<<<<<<< HEAD
        if(!valid(str)){
            printf("Invalid input\n");
=======
        if(win(str, ans)){
            break;
>>>>>>> output
        }else{
            response(str, ans);
        }
    }
    printf("You won!\n");
}

14.

身為 main branch 的維護者, A 要負責將 feature branches 合併進 main

A

B

C

git add .
git commit -m "merge output"

5

7

8

8

10

11

input

12

output

13

main

14

main

5

7

8

8

10

11

input

12

output

13

14

main

git push origin main
gcc game.c -o game.exe
./game.exe
  • 更系統化的分工合作

  • 一個團隊的紀律

  • avoid conflicts!!

5

7

8

8

10

11

input

12

output

13

14

main

大家都想在 main  branch 上做事

\(\Rightarrow\) 衝突多、merge 多、歷史記錄亂

每個人在自己的 branch 上做事

彼此不互相干擾

  • 同一個 branch 盡量少人操作
  • main 作為正式版本
  • merge 進主要分支前要 code review

Feature Branching 

後話

git rebase

A

B

C

A

B

C

A

B

C

B

C

A

B

C

git merge
git rebase

A

B

C

A

B

C

A

B

C

B

D

A

B

C

git merge
git rebase
git merge
git rebase

A

B

C

B

D

A

B

C

  • commit D 幾乎沒有任何意義
  • merge 會產生多餘的 commit
  • merge 會造成非線性的歷史紀錄
  • rebase 就是將 C 的修改重新作用在 B 上面
  • rebase 兩邊的 commit 鏈越長,就容易產生越多的 conflict
  • rebase 對版本樹結構影像較大

Playground

Init

git init
git config [--global] user.name <bob>
git config [--global] user.email <bob@csie.ntu.edu.tw>

commit!

// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("clear_screen\n");
}

void print_screen() {
    printf("print_screen\n");
}

#endif
// donut.c
#include <unistd.h>
#include "function.h"

int main() {
    clear_screen() ;

    for(;;) {
        print_screen() ;
    }
}
# Makefile
run:
	gcc donut.c -lm -Wall -Wextra

initial

// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen() {
    printf("print_screen\n");
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}


#endif
// donut.c
#include <unistd.h>
#include "function.h"

int main() {
    clear_screen() ;
    hide_cursor() ;

    for(;;) {
        reset_cursor() ;
        print_screen() ;
        usleep(30000) ;
    }
}

handle frames

git add <filename>
git status
git commit -m "<message>"
git restore [--staged] <filename>
git rm [--cache] <filename>

Time Travel!

// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen(float A, float B) {
    printf("print_screen %f %f\n", A, B);
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}


#endif
// donut.c
#include <unistd.h>
#include "donut.h"
#include "function.h"

int main() {
    float A = 0 ;
    float B = 0 ;
    clear_screen() ;
    hide_cursor() ;

    for(;;) {
        reset_cursor() ;
        print_screen(A, B) ;

        A += omega_A ;
        B += omega_B ;
        usleep(30000) ;
    }
}

add momentum

// donut.h
#ifndef DONUT_H
#define DONUT_H

const float omega_A = 0.07;
const float omega_B = 0.04;

#endif
git log
git reset [--soft|--hard] <commit>
git stash
git stash pop
git checkout <hash>

GitHub

1.

創建 GitHub 帳號 & 新增 ssh key

github.com/signup

github.com/settings/ssh/new

A

B

C

2.

創建 repository

右上角 + 號

> New Repo

> Repo name 隨便取

> Create repo

A

B

C

3.

新增協作者

Repo 主頁

> Settings

> Collaborators

> Add people

A

B

C

local

ssh-keygen
cat ~/.ssh/id_rsa.pub

github

4.

把 repo clone 到本機上

github.com/username/repo-name

A

B

C

git@github.com:username/repo-name.git
cd /path/to/any/directory
git clone <repo_url>
cd repo-name
git remote add origin git@github.com:Ccucumber12/myrepo.git
git branch -M main
git push -u origin main
git clone git@github.com:Ccucumber12/myrepo.git

Push n' Pull

// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen(float A, float B) {
    printf("print_screen %f %f\n", A, B);
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}

coordinate get_projection(float theta, float phi, float A, float B) {
    return (coordinate){ .x=0, .y=0, .z=0 };
}

float get_luminance(float theta, float phi, float A, float B) {
    return 0;
}

void get_output(float L, coordinate pos) {
}

#endif
// donut.c
#include <unistd.h>
#include "donut.h"
#include "function.h"

int main() {
    float A = 0 ;
    float B = 0 ;
    clear_screen() ;
    hide_cursor() ;

    for(;;) {
        for (float theta=0; theta < 2*pi; theta += dtheta) {
            for(float phi=0; phi < 2*pi; phi += dphi) {
                coordinate pos = get_projection(theta, phi, A, B) ;
                float L = get_luminance(theta, phi, A, B) ;
                get_output(L, pos) ;
            }
        }
        reset_cursor() ;
        print_screen(A, B) ;

        A += omega_A ;
        B += omega_B ;
        usleep(30000) ;
    }
}

A: more structures

// donut.h
#ifndef DONUT_H
#define DONUT_H

const float pi = 3.1415926 ;
const float dtheta = 0.07 ;
const float dphi   = 0.02 ;

const float omega_A = 0.07;
const float omega_B = 0.04;

typedef struct Tcoordinate {
    int x ;
    int y ; 
    float z ;
} coordinate ;

#endif
// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen(float A, float B) {
    printf("print_screen %f %f\n", A, B);
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++)
            putchar(output[j][i]);
        putchar('\n');
    }
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}

void reset_values() {
    memset(output, (int)' ', sizeof(output)) ;
    memset(bestOOZ, 0, sizeof(bestOOZ)) ;
}

coordinate get_projection(float theta, float phi, float A, float B) {
    return (coordinate){ .x=0, .y=0, .z=0 };
}

float get_luminance(float theta, float phi, float A, float B) {
    return 0;
}

void get_output(float L, coordinate pos) {
}

#endif
// donut.c
#include <unistd.h>
#include "donut.h"
#include "function.h"

int main() {
    float A = 0 ;
    float B = 0 ;
    clear_screen() ;
    hide_cursor() ;

    for(;;) {
        reset_values() ;
        for (float theta=0; theta < 2*pi; theta += dtheta) {
            for(float phi=0; phi < 2*pi; phi += dphi) {
                coordinate pos = get_projection(theta, phi, A, B) ;
                float L = get_luminance(theta, phi, A, B) ;
                get_output(L, pos) ;
            }
        }
        reset_cursor() ;
        print_screen(A, B) ;

        A += omega_A ;
        B += omega_B ;
        usleep(30000) ;
    }
}

B: screen size & array

// donut.h
#ifndef DONUT_H
#define DONUT_H

#define width 30
#define height 30

const float pi = 3.1415926 ;
const float dtheta = 0.07 ;
const float dphi   = 0.02 ;

const float omega_A = 0.07;
const float omega_B = 0.04;

typedef struct Tcoordinate {
    int x ;
    int y ; 
    float z ;
} coordinate ;

char output[height][width] ;
float bestOOZ[height][width] ;

#endif
git pull --ff-only
git push

Rejected

// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen(float A, float B) {
    printf("print_screen %f %f\n", A, B);
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++)
            putchar(output[j][i]);
        putchar('\n');
    }
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}

void reset_values() {
    memset(output, (int)' ', sizeof(output)) ;
    memset(bestOOZ, 0, sizeof(bestOOZ)) ;
}

coordinate get_projection(float theta, float phi, float A, float B) {
    float costheta = cos(theta), sintheta = sin(theta);
    float cosphi = cos(phi), sinphi = sin(phi);
    float cosA = cos(A), sinA = sin(A) ;
    float cosB = cos(B), sinB = sin(B) ;

    // the x,y coordinate of the circle before revolving
    float circlex = R2 + R1*costheta;
    float circley = R1*sintheta;

    // final 3D (x,y,z) coordinate after rotations 
    coordinate pos ;
    float x = circlex*(cosB*cosphi + sinA*sinB*sinphi) - circley*cosA*sinB ; 
    float y = circlex*(sinB*cosphi - sinA*cosB*sinphi) + circley*cosA*cosB ;
    float z = donut_distance + cosA*circlex*sinphi + circley*sinA ;

    // (x,y,z) after projection
    pos.x = (int) (width/2 + render_distance*x/z);
    pos.y = (int) (height/2 - render_distance*y/z);
    pos.z = z ;

    return pos ;
}

float get_luminance(float theta, float phi, float A, float B) {
    // calculate luminance, range from -sqrt(2) to +sqrt(2). 
    return cos(phi)*cos(theta)*sin(B) - cos(A)*cos(theta)*sin(phi) - sin(A)*sin(theta) + cos(B)*(cos(A)*sin(theta) - cos(theta)*sin(A)*sin(phi));
}

void get_output(float L, coordinate pos) {
}

#endif

A: get_projection + get_luminance

// donut.h
#ifndef DONUT_H
#define DONUT_H

#define width 30
#define height 30

const float pi = 3.1415926 ;
const float dtheta = 0.07 ;
const float dphi   = 0.02 ;

const float omega_A = 0.07;
const float omega_B = 0.04;

const float R1 = 1 ;
const float R2 = 2 ;
const float donut_distance = 5 ;
const float render_distance = width*donut_distance*3/(8*(R1+R2));

typedef struct Tcoordinate {
    int x ;
    int y ; 
    float z ;
} coordinate ;

char output[height][width] ;
float bestOOZ[height][width] ;

#endif
// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen(float A, float B) {
    printf("print_screen %f %f\n", A, B);
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++)
            putchar(output[j][i]);
        putchar('\n');
    }
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}

void reset_values() {
    memset(output, (int)' ', sizeof(output)) ;
    memset(bestOOZ, 0, sizeof(bestOOZ)) ;
}

coordinate get_projection(float theta, float phi, float A, float B) {
    return (coordinate){ .x=0, .y=0, .z=0 };
}

float get_luminance(float theta, float phi, float A, float B) {
    return 0;
}

void get_output(float L, coordinate pos) {
    // larger ooz means closer to viewer, reset output for closer z
    float ooz = 1 / pos.z ; // one over z
    if(ooz > bestOOZ[pos.y][pos.x]) {
        bestOOZ[pos.y][pos.x] = ooz ;
        output[pos.y][pos.x] = L > 5 ? '@' : '.';
    }
}

#endif

B: get_output

git push # reject 
git pull --ff-only
git merge origin/main
git push

Branching

// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen(float A, float B) {
    printf("print_screen %f %f\n", A, B);
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++)
            putchar(output[j][i]);
        putchar('\n');
    }
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}

void reset_values() {
    memset(output, (int)' ', sizeof(output)) ;
    memset(bestOOZ, 0, sizeof(bestOOZ)) ;
}

coordinate get_projection(float theta, float phi, float A, float B) {
    float costheta = cos(theta), sintheta = sin(theta);
    float cosphi = cos(phi), sinphi = sin(phi);
    float cosA = cos(A), sinA = sin(A) ;
    float cosB = cos(B), sinB = sin(B) ;

    // the x,y coordinate of the circle before revolving
    float circlex = R2 + R1*costheta;
    float circley = R1*sintheta;

    // final 3D (x,y,z) coordinate after rotations 
    coordinate pos ;
    float x = circlex*(cosB*cosphi + sinA*sinB*sinphi) - circley*cosA*sinB ; 
    float y = circlex*(sinB*cosphi - sinA*cosB*sinphi) + circley*cosA*cosB ;
    float z = donut_distance + cosA*circlex*sinphi + circley*sinA ;

    // (x,y,z) after projection
    pos.x = (int) (width/2 + render_distance*x/z);
    pos.y = (int) (height/2 - render_distance*y/z);
    pos.z = z ;

    return pos ;
}

float get_luminance(float theta, float phi, float A, float B) {
    // calculate luminance, range from -sqrt(2) to +sqrt(2). 
    return cos(phi)*cos(theta)*sin(B) - cos(A)*cos(theta)*sin(phi) - sin(A)*sin(theta) + cos(B)*(cos(A)*sin(theta) - cos(theta)*sin(A)*sin(phi));
}

void get_output(float L, coordinate pos) {
}

#endif

A: get_projection + get_luminance

// donut.h
#ifndef DONUT_H
#define DONUT_H

#define width 30
#define height 30

const float pi = 3.1415926 ;
const float dtheta = 0.07 ;
const float dphi   = 0.02 ;

const float omega_A = 0.07;
const float omega_B = 0.04;

const float R1 = 1 ;
const float R2 = 2 ;
const float donut_distance = 5 ;
const float render_distance = width*donut_distance*3/(8*(R1+R2));

typedef struct Tcoordinate {
    int x ;
    int y ; 
    float z ;
} coordinate ;

char output[height][width] ;
float bestOOZ[height][width] ;

#endif
// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen(float A, float B) {
    printf("print_screen %f %f\n", A, B);
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++)
            putchar(output[j][i]);
        putchar('\n');
    }
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}

void reset_values() {
    memset(output, (int)' ', sizeof(output)) ;
    memset(bestOOZ, 0, sizeof(bestOOZ)) ;
}

coordinate get_projection(float theta, float phi, float A, float B) {
    return (coordinate){ .x=0, .y=0, .z=0 };
}

float get_luminance(float theta, float phi, float A, float B) {
    return 0;
}

void get_output(float L, coordinate pos) {
    // larger ooz means closer to viewer, reset output for closer z
    float ooz = 1 / pos.z ; // one over z
    if(ooz > bestOOZ[pos.y][pos.x]) {
        bestOOZ[pos.y][pos.x] = ooz ;
        output[pos.y][pos.x] = L > 5 ? '@' : '.';
    }
}

#endif

B: get_output

# create local branch
git branch <bob>
git checkout <bob>
git push -u origin <bob>

# merge and push
git checkout main
git pull
git merge <bob>

# pull alice branch and checkout
git pull
git switch <alice>
git checkout -b <alice> origin/<alice>

git branch -d <bob>       # delete local branch
git push origin -d <bob>  # delete remote branch
git pull --prune          # remove remote deleted branch 

CONFLICT

// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen(float A, float B) {
    printf("print_screen %f %f\n", A, B);
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++)
            putchar(output[j][i]);
        putchar('\n');
    }
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}

void reset_values() {
    memset(output, (int)' ', sizeof(output)) ;
    memset(bestOOZ, 0, sizeof(bestOOZ)) ;
}

coordinate get_projection(float theta, float phi, float A, float B) {
    float costheta = cos(theta), sintheta = sin(theta);
    float cosphi = cos(phi), sinphi = sin(phi);
    float cosA = cos(A), sinA = sin(A) ;
    float cosB = cos(B), sinB = sin(B) ;

    // the x,y coordinate of the circle before revolving
    float circlex = R2 + R1*costheta;
    float circley = R1*sintheta;

    // final 3D (x,y,z) coordinate after rotations 
    coordinate pos ;
    float x = circlex*(cosB*cosphi + sinA*sinB*sinphi) - circley*cosA*sinB ; 
    float y = circlex*(sinB*cosphi - sinA*cosB*sinphi) + circley*cosA*cosB ;
    float z = donut_distance + cosA*circlex*sinphi + circley*sinA ;

    // (x,y,z) after projection
    pos.x = (int) (width/2 + render_distance*x/z);
    pos.y = (int) (height/2 - render_distance*y/z);
    pos.z = z ;

    return pos ;
}

float get_luminance(float theta, float phi, float A, float B) {
    // calculate luminance, range from -sqrt(2) to +sqrt(2). 
    return cos(phi)*cos(theta)*sin(B) - cos(A)*cos(theta)*sin(phi) - sin(A)*sin(theta) + cos(B)*(cos(A)*sin(theta) - cos(theta)*sin(A)*sin(phi));
}

void get_output(float L, coordinate pos) {
    if (pos.x < 0 || pos.x >= width || pos.y < 0 || pos.y >= height)
        return ;
    // larger ooz means closer to viewer, reset output for closer z
    float ooz = 1 / pos.z ; // one over z
    if(ooz > bestOOZ[pos.y][pos.x]) {
        bestOOZ[pos.y][pos.x] = ooz ;
        output[pos.y][pos.x] = L > 5 ? '@' : '.';
    }
}

#endif

A: fix size error

// function.h
#ifndef FUNCTION_H
#define FUNCTION_H

#include <stdio.h>
#include <math.h>
#include <string.h>

void clear_screen() {
    printf("\x1b[2J");
}

void print_screen(float A, float B) {
    printf("print_screen %f %f\n", A, B);
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++)
            putchar(output[j][i]);
        putchar('\n');
    }
}

void hide_cursor() {
    printf("\033[?25l") ;
}

void reset_cursor() {
    printf("\x1b[H");
}

void reset_values() {
    memset(output, (int)' ', sizeof(output)) ;
    memset(bestOOZ, 0, sizeof(bestOOZ)) ;
}

coordinate get_projection(float theta, float phi, float A, float B) {
    float costheta = cos(theta), sintheta = sin(theta);
    float cosphi = cos(phi), sinphi = sin(phi);
    float cosA = cos(A), sinA = sin(A) ;
    float cosB = cos(B), sinB = sin(B) ;

    // the x,y coordinate of the circle before revolving
    float circlex = R2 + R1*costheta;
    float circley = R1*sintheta;

    // final 3D (x,y,z) coordinate after rotations 
    coordinate pos ;
    float x = circlex*(cosB*cosphi + sinA*sinB*sinphi) - circley*cosA*sinB ; 
    float y = circlex*(sinB*cosphi - sinA*cosB*sinphi) + circley*cosA*cosB ;
    float z = donut_distance + cosA*circlex*sinphi + circley*sinA ;

    // (x,y,z) after projection
    pos.x = (int) (width/2 + render_distance*x/z);
    pos.y = (int) (height/2 - render_distance*y/z);
    pos.z = z ;

    return pos ;
}

float get_luminance(float theta, float phi, float A, float B) {
    // calculate luminance, range from -sqrt(2) to +sqrt(2). 
    return cos(phi)*cos(theta)*sin(B) - cos(A)*cos(theta)*sin(phi) - sin(A)*sin(theta) + cos(B)*(cos(A)*sin(theta) - cos(theta)*sin(A)*sin(phi));
}

char get_pattern(float L) {
    // L is in range of -sqrt(2) to +sqrt(2)
    // luminance_index is now in the range 0..11 (8*sqrt(2) = 11.3)
    int luminance_index = L * 8 ; 
    return pattern[luminance_index] ;
}

void get_output(float L, coordinate pos) {
    // check if out of range
    if (L < 0)
        return ;
    // larger ooz means closer to viewer, reset output for closer z
    float ooz = 1 / pos.z ; // one over z
    if(ooz > bestOOZ[pos.y][pos.x]) {
        bestOOZ[pos.y][pos.x] = ooz ;
        output[pos.y][pos.x] = get_pattern(L) ;
    }
}

#endif

B: better display

// donut.h
#ifndef DONUT_H
#define DONUT_H

#define width 30
#define height 30

const float pi = 3.1415926 ;
const float dtheta = 0.07 ;
const float dphi   = 0.02 ;

const float omega_A = 0.07;
const float omega_B = 0.04;

const float R1 = 1 ;
const float R2 = 2 ;
const float donut_distance = 5 ;
const float render_distance = width*donut_distance*3/(8*(R1+R2));

const char pattern[] = ".,-~:;=!*#$@" ;

typedef struct Tcoordinate {
    int x ;
    int y ; 
    float z ;
} coordinate ;

char output[height][width] ;
float bestOOZ[height][width] ;

#endif
git merge <bob> conflict !
vim <file>
git add <file>
git commit -m "message"
git push

Resources

  • https://git-scm.com/doc
  • https://docs.github.com/en
  • https://www.atlassian.com/git/tutorials/learn-git-with-bitbucket-cloud
Made with Slides.com