Git Basic. 基礎 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 資料夾,這樣就完成了:

$ 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 下面我會想要放很多 projects,而不是只有單一 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/

$ 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 下新增一個檔案:

$ touch 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 statusfirst_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:
~

這是因為我們剛剛做的事情是盤古開天第一件事,所以沒有 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 .
diffgit 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(+)

$ 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:

<透過 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 已經被上傳了:

<first_try.txt 已經被上傳>

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

<first_try.txt 的內容>

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

<first_try.txt 的 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(-)

$ 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 上就又可以看到了

<first_try.txt>

Pull

前面講的是本地創造然後上傳,接下來將的就是下載,這是一個更常用的情境,有可能是下載別人的 repo 做進一步更改再上傳回去做貢獻,或者是下載自己的 repo 做進一步的開發。

我們一樣就用剛剛已經在 github 上創造的 test 這個 repo 為例,假設這個 repo 就是我今天要下載下來本地編輯的 repo:

到 github 的該 repo,就會看到 clone/download 的選項:

<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 上就又可以看到我們的改動了:

<first_try.txt>


Author: Chris Chung
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Chris Chung !
  TOC