跳到主要內容

[Development] git

本篇介紹幾個 git 會用到的基本 command line 指令

安裝 on Mac OS

[tested on macOS Catalina 10.15.1]

(可以用 Brew 安裝,若尚未安裝 Brew: https://brew.sh/index_zh-tw)

$ brew install git
$ git --version

git version 2.21.0 (Apple Git-122.2)

  github 官網就有提供 GUI 的應用,想用 GUI 的直接去下載安裝就行了,也有簡單明瞭的教學,非常容易。另外也有好幾個第三方的好用 GUI 介面,Google 一下比較一下選自己喜歡的也行。
  但以下還是用 command line 的方法來操作,因為這還是最 general 到哪都可以用的基本方法。因為實務上,比如 code 都放在公司的 server,你可能也是都要 ssh 到 sever 上去改 code,改完之後要上傳到 github。而公司的 server 就是一台 Linux 環境,很可能是沒有提供 GUI 讓你使用的,所以你就只能用 command line 的方式完成 git 的上傳。

Create Repo

  去本地一個你想要放置 git 專案的地方,比如我想把我之後的 git code 都放在我 Mac local 的 /Users/chungchris/git

$ cd /Users/chungchris/git
$ git init

  就會看到在此 git 目錄下產生一個隱藏的 .git 資料夾,這樣就完成了:

(base) Chris-MBP:git chungchris$ ls -al
total 0
drwxr-xr-x   3 chungchris  staff    96 11 21 11:01 .
drwxr-xr-x+ 53 chungchris  staff  1696 11 21 10:45 ..
drwxr-xr-x   9 chungchris  staff   288 11 21 11:01 .git

  所以如果之後想要把他刪掉,其實就是把 .git 刪掉就行了:

$ rm -rf .git/

  然而,一般來說,/Users/chungchris/git 下面我會想要放很多 project,而不是只有單一 project。而剛剛做的 git init 其實就是在本地新增一個 repo 的意思,而一個 project 可能會包含一個到數個 repo。
  所以我們更想做的應該是要在 /Users/chungchris/git 下面創一個 project 專屬的資料夾,然後把這個資料夾 init 為一個 git 的 repo,或者甚至是在 project 的資料夾下創造多個 repo。

$ pwd
/Users/chungchris/git

$ mkdir project20191121
$ cd project20191121/

$ git init
Initialized empty Git repository in /Users/chungchris/git/project20191121/.git/
(base) Chris-MBP:project20191121 chungchris$ ls -al
total 0
drwxr-xr-x  3 chungchris  staff   96 11 21 11:25 .
drwxr-xr-x  4 chungchris  staff  128 11 21 11:25 ..
drwxr-xr-x  9 chungchris  staff  288 11 21 11:25 .git

  比如我上面就創了一個名為 project20191121 的 folder,並將他做 git init,於是這個 folder 的 path 就從此成為了一個 git repo,如果你將來不希望他是一個 git repo,而只是一個普通的 folder,那就將 .git 刪掉就可以了。

  接下來我們設置身份,這在之後上傳雲端 git 的時候需要用到。
  首先他必須符合我們的 github 帳號,比如這是我的 github 帳號是 "chungchris"
那我就在本地這樣設置,注意大小寫要一樣才行:

$ git config --global user.name "chungchris"
$ git config --global user.email "imchungchris@hotmail.com"

$ git config --get --global user.name
chungchris
$ git config --get --global user.email
imchungchris@hotmail.com

Creste File

  接著我們試著在此 repo 下新增一個檔案:

$ vim first_try.txt
$ git status 
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)

first_try.txt


nothing added to commit but untracked files present (use "git add" to track)

  比如上面我就新增了一個名為 first_try.txt 的文檔,然後我就可以透過 git status 來看一下我目前這個 repo 下的狀態,git status 在往後都會是一個非常基本且常用的指令。
  我們會看到 git 告訴我們在 master 這個 branch 上面,多出了一個尚未被 track/commit 的檔案 first_try.txt,並建議用 git add 指令將其納入 track。(branch 的概念我們往後再敘述,現在姑且都先在 master 上操作)

Commit

  於是我們就依照他的建議做 add:

$ git add first_try.txt 
$ git status 
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

new file:   first_try.txt


  此時再看一下 git status,first_try.txt 就變成了綠色,也就是被納入 track 了。

  接著 git 建議我們將其 commit:

$ git commit first_try.txt

  就會出現 commit message 的編輯頁:

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
#
# Initial commit
#
# Changes to be committed:
#       new file:   first_try.txt
#

~  

  你可以在此頁當中說明你 commit 這一筆 change 的原因,比如我們現在是新增了一個 file,那我們為什麼要新增這個 file,用途是什麼。未來如果我們做的事情是編輯了某個既有的 file,那原因可就要敘述為什麼做此改動,改動的方式是什麼之類的。這在實務上是非常重要的,因為其他在這個 project 上工作的人就可以透過這個 commit message 來快速知道- 誰、在哪個時間點、因為什麼原因、做了什麼改動,如果描述得不清不楚,那就變得要實際去做 code change review,那就比較辛苦耗時了。

  這裏我們就編輯一下 commit message,理由寫上 "Just for test",然後用 ESC 和 ":wq" 儲存完成編輯。
(另外,下方 '#' 開頭的幾行在之後都是不會出現的)

Just for test
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
#
# Initial commit
#
# Changes to be committed:
#       new file:   first_try.txt

#


$ git commit first_try.txt 

[master (root-commit) b028b64] Just for test
 1 file changed, 2 insertions(+)

 create mode 100644 first_try.txt

  到此 commit 就完成了,於是我們再看一次 git status:

$ git status 

On branch master

nothing to commit, working tree clean

  就告訴我們目前所做的更動都已經 commit 了。
  如果想要更改 commit msg 也是可以的:

$ git commit --amend

  如果想要做復原呢?
  比如我們剛剛在這個 repo 下做的事情就是新增了一個 first_try.txt 然後將其 commit 了,如果我要回到還沒有做這個動作的時候,我們可以用 reset 指令。
  但這時候我們可能會遇到一錯誤:

$ git reset HEAD^

fatal: ambiguous argument 'HEAD^': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:

'git <command> [<revision>...] -- [<file>...]

  這是因為我們剛剛做的事情是盤古開天第一件事,所以沒有 HEAD。所以我們嘗試再多做一點,比如現在我們再進一步對已經 commit 的 first_try.txt 做一次編輯,git status 就會看到 first_try.txt 又變為紅色,狀態顯示為 modified。

$ vim first_try.txt 
$ git status .

On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

modified:   first_try.txt


no changes added to commit (use "git add" and/or "git commit -a")

  這裏再介紹一個非常基本且常用的指令 git diff

$ git diff .

diff --git a/first_try.txt b/first_try.txt
index 0b1076d..751d415 100644
--- a/first_try.txt
+++ b/first_try.txt
@@ -1,2 +1,3 @@
 firstfirstfirst
+2222


  透過這個指令,可以看到我們所做的更改是在 first_try.txt 這個檔案新增了一行 "2222" 的文字。

  於是我們跟剛剛一下做 add 和 commit

$ git add -A .

$ git commit -m "make a small change"
[master ed597bd] make a small change
 1 file changed, 1 insertion(+)
(base) Chris-MBP:project20191121 chungchris$ git status .
On branch master

nothing to commit, working tree clean

  "git add -A ." 會將當前目錄下所有 untracked 都 add;
  "git commit -m" 在你的 commit message 只有短短幾個字的時候是很方便的,比如我現在想寫的 commit msg 只有 "make a small change";
  也可以用 “git commit -a -m " 進一步化兩行為一行。
  此時我們再看一下 git status,就又變為 clean 了。

  這個時候我們就可以嘗試做 reset,讓他變為還沒有新增 2222 那一行的狀態:

$ git reset HEAD^
Unstaged changes after reset:
M first_try.txt

$ git status .
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

modified:   first_try.txt


no changes added to commit (use "git add" and/or "git commit -a")

  就可以看到做了 reset 之後,first_try.txt 又變回紅色的 untracked 狀態了,但此時 2222 還是在的喔,只是我們剛剛做的 add/commit 已經被復原了。
  reset HEAD 後面的上標三角是指次數,因為我現在只要回復一級,所以只有一個三角,若想要直接復原兩次 commit 那就給他兩個三角 HEAD^^

  現在 first_try.txt 已經回到了 untracked 狀態,那要怎麼真的把 2222 那行刪掉呢?可以用 checkout 指令。
  這裡要用 git 的概念來理解,我們剛剛做過兩件事,第一件事是新增了 first_try.txt,第二件事是進一步再編輯了一次 first_try.txt 新增了 "2222" 這一行,而第一件事已經 commit 了,第二件事曾經 commit 過但又被 reset 了,所以現在等於是第二件事情未 commit 的狀態。
  也就是說現在對 git 來說,他承認的官方狀態是我們做完第一件事的狀態,所以當我們使用了 checkout 這個指令,意思就是把當下 repo 承認的官方狀態給 checkout 出來,而這就覆蓋那個被我們就進一步編輯過的 first_try.txt,所以效果上就跟復原一樣。

$ git checkout .
Updated 1 path from the index

$ git status .
On branch master

nothing to commit, working tree clean

$ cat first_try.txt 
firstfirstfirst

  可以看到做完 checkout 後,repo 狀態又變為 clean 了,於是也可以理解所謂 clean 都是與 git 承認的官方狀態去做比較的。

Upload

  我們在本地端有一個 repo,在雲端也給他個相應的 repo,名稱可以不同的,因為對於本地來說,他其實就可以被視為僅是一個有了 git 屬性的 folder,跟實際上 repo 的名字沒有必然直接關係。
  比如我透過 github 網頁創了一個 repo:

  上面可以看到一行有 remote 的指令,顧名思義如果我們在 local 執行了這行指令,那原本單純都只是在我們 local 操作的 git repo,就和這個剛剛在 github 上創造的名為 test 的 repo 產生了連結:

$ git remote add origin https://github.com/chungchris/test.git

  接著我們便可以把 local 做的改動傳到雲端了,當然指的是已經 local commit 的改動。
  上傳的這個動作在 git 中叫做 push:

$ git push origin master
Username for 'https://github.com': chungchris
Password for 'https://chungchris@github.com': 
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 240 bytes | 240.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/chungchris/test.git

 * [new branch]      master -> master

  上面可以看到他要求我輸入帳號密碼,這可以藉由 config 簡化,稍後再述。
  此時我們去網頁上看,就可以看到 first_try.txt 已經被上傳了:


  並且它的內容是我們早先 local commit 版本:

  點進去就看了了我們的 commit msg 以及做過的改動:

  另外,多筆 commit 是可以一次 push 到 remote 的,只是上面例子當中只有一筆。

  回到剛剛的 git push 指令,我們下的是 "git push origin master"。
  第一個參數是 remote 是誰,因為我們更早下過
    "git remote add origin https://github.com/chungchris/test.git",
  所以自然 git 就知道這裡的 origin 指的是 https://github.com/chungchris/test.git
  第二個參數是 branch 名稱,一樣 branch 的概念我們之後再一次一起敘述。
  但這裡可以先提的是,我們本地的 repo 已經和 remote 的 repo 產生連結,實務上,我們基本上就認定在這個 folder 底下的工作就都 default 視為要傳到該特定 remote 的特定 branch 了,如果這個需求成立,我們可以做進一步的綁定

$ git checkout master
Already on 'master'

$ git branch -u origin/master
Branch 'master' set up to track remote branch 'master' from 'origin'.

  從此我們要在這個 local repo 底下工作,要 push 東西到 remote 時,就直接 git push 就可以了,不需再指定 remote 和 branch。上述也可以透過第一次做 push 時加上 -u 參數來完成,u 指的是 upstream。
  接下來我們嘗試在 local 再對 first_try.txt 做進一步編輯並 commit 後上傳:

$ vim first_try.txt 

$ git status .
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

modified:   first_try.txt

no changes added to commit (use "git add" and/or "git commit -a")

$ git commit -a -m "add one more line"
[master c89866e] add one more line
 1 file changed, 1 insertion(+), 1 deletion(-)
(base) Chris-MBP:project20191121 chungchris$ git status .
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 293 bytes | 293.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/chungchris/test.git

   7f2499f..c89866e  master -> master

在 github 上就又可以看到了


Pull

  前面講的是本地創造然後上傳,接下來將的就是下載,這是一個更常用的情境,有可能是下載別人的 repo 做進一步更改再上傳回去做貢獻,或者是下載自己的 repo 做進一步的開發。
  我們一樣就用剛剛已經在 github 上創造的 test 這個 repo 為例,假設這個 repo 就是我今天要下載下來本地編輯的 repo:
  到 github 的該 repo,就會看到 clone/download 的選項:

  這裏我在本地創了個新 folder 然後透過 git clone 把 code 抓下來:

$ mkdir repo_test
$ cd repo_test/
$ pwd
/Users/chungchris/repo_test

$ git clone https://github.com/chungchris/test.git
Cloning into 'test'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 0), reused 3 (delta 0), pack-reused 3
Unpacking objects: 100% (6/6), done.

$ ls -al
total 0
drwxr-xr-x   3 chungchris  staff    96 11 21 13:12 .
drwxr-xr-x+ 54 chungchris  staff  1728 11 21 13:09 ..

drwxr-xr-x   4 chungchris  staff   128 11 21 13:12 test

$ cd test/
$ ls -al
total 8
drwxr-xr-x   4 chungchris  staff  128 11 21 13:12 .
drwxr-xr-x   3 chungchris  staff   96 11 21 13:12 ..
drwxr-xr-x  12 chungchris  staff  384 11 21 13:12 .git
-rw-r--r--   1 chungchris  staff   52 11 21 13:12 first_try.txt

  可以看到抓下來的 test 本身就已經維持是一個 git 的 repo 了。
  於是我們嘗試對 first_try.txt 做編輯,然後一樣 commit 後 push:

$ vim first_try.txt 

$ git commit -a -m "make a contribution"
[master 40b2085] make a contribution
 1 file changed, 3 insertions(+), 1 deletion(-)

$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 294 bytes | 294.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/chungchris/test.git

   c89866e..40b2085  master -> master

  在 github 上就又可以看到我們的改動了:





留言

這個網誌中的熱門文章

[Coding] Compiler

Something about compiling. #compiler #link #gcc Reference PTT LinuxDev, 作者: cole945 (躂躂..) 看板: LinuxDev, 標題: [心得] 用gcc 自製Library, 時間: Sun Nov 5 04:15:45 2006 Static Link Compile 時將 library 加入程式碼,執行快但佔空間,code size 和 mem 使用都比較多 Compile source codes to generate object files $ gcc -c file1.c file2.c file3.c -c 編出 object 檔 Create a static library named libmylib.a $ ar rcs lib mylib .a file1.o file2.o file3.o 把一堆 object 檔用 ar(archiver) 包裝集合起來,檔名以`.a’ 結尾 Using a Static Library to generate a executable files $ gcc -o main main.c -L. -l mylib -L: the directory of the library. 可以指定多次 -LDIR -l: the name of the library (注意 藍色 部分是 match 的) Dynamic Link Compile 時不將 library 加入程式碼,執行程式的時後再將 library 載入程式碼,若有多個程式共用同一個 library,只需載一個 library 進 memory Compile source code $ gcc -c -fPIC file1.c file2.c file3.c -fPIC 表示要編成 position-independent code,這樣不同 process 載入 shared library 時,library 的程式和資料才能放到記憶體不同位置。-fPIC 較通用於不同平台,但產生的 code 較大,而且編譯速度較慢 Create a shared library...

Let's Move On

今天決定要建個部落格 身為資工系的學生, 又那麼愛做筆記 XD 卻到今天才想做這件事似乎有點落漆 還沒明確知道想用來記錄些什麼, 有可能只是與程式相關的東西, 也有可能會很雜 但也很有可能過了一年什麼屁都沒有, 到時候也可以拿出來嘲笑自己一下自我檢討一番 Decide creating the blog today. It seems abnormal for a computer science student to do this so late. After all, I like taking note a lots... Still not sure about what am gonna write here. Maybe all about programming. Maybe it will be really mixed. Nevertheless... very likely that it will still be empty after a year... Then I can open it and piss myself. 迅速 Google 了一下該用哪個平台, 只想找個簡單好用的 外貌先決下 wordpress 和 blogger 脫穎而出, 進一步看了一下覺得並沒有複雜架站的需求所以最後選了 blogger