Linux伺服器 Linux伺服器

資工所專業課程上課教材

資工所專業課程 > 課程內容 > 專題 - 使用 git 進行版本控制 - 多人共同開發之容器設定

專題 - 使用 git 進行版本控制 - 多人共同開發之容器設定

上次更新日期 2020/10/04

前面提到的 git 使用的方式,大部分是在本機上面,自己一個人獨立開發軟體/網頁/其他工作的流程, 那如果你有需要進行多人共同開發的行為時,該如何處理?這就是一般使用 GitHub 的緣由!不過,或許因為某些機密開發, 你需要將 Server 放置在公司內部進行開發,這時,自己處理一部內部網路來管理,可能會是一個比較好的作法。 我們這裡提供一個簡單的內網 ssh 協同開發的專案範本,讓多人可以同時進行同一個專案的開發。

建立裸容器的權威容器

事實上,我們前面的章節談到的 git 操作,都是屬於本機的『開發容器』,意思是,我可以在這個容器內自由的開發、分支、合併、刪除等。 但是,畢竟這只是單一個人在某個目錄內的自由開發,對於小型專案來說,還不成問題。但是對於大型專案來說,可能就會產生部份的狀況! 例如,你願意讓某些人進入你的開發目錄惡搞嘛?可能會損壞到你原本的開發行為啊!

所以,一般來說,如果在同一部系統上面進行開發時,大家還是習慣在自己的開發容器進行發展,等到發展的差不多, 再將程式馬上傳/下載到某個統一管理的地方,這樣,就可以保有獨立開發,並且在統一管理的位置上面進行送交、合併的行為! 這對於需要不同團隊合作開發的較大型專案來說,是相當重要的!

  1. 幾個常見的基本名稱:
    1. 開發容器:
      所謂的開發容器,就是開發目錄底下具有軟體檔案的環境,那就是開發容器。一般來說,在專案目錄底下具有 .git 隱藏目錄的,那就是開發容器!我們前面的章節所建立的容器,無論是 git clone 產生,還是 git init 產生的, 都屬於這一類型!在該目錄底下,可以進行開發、送交、合併等任務。
    2. 裸容器:
      例如上面的圖示,那個所謂的『權威容器』,通常指的就是裸容器。裸容器目錄底下並沒有 .git 隱藏目錄, 你可以說,裸容器本身就是 .git 底下的資料!也就是說,裸容器本身不含檔案結構,而主要是紀錄該專案內的所有變動。 這個裸容器可以讓使用者使用 git push, git pull 等任務。
  2. 觀察裸容器與開發容器:
    1. 兩個容器的初始化方式差異在 --bare 而已:
      # cd /dev/shm
      # git init proa
      Initialized empty Git repository in /dev/shm/proa/.git/
      
      # ll -a proa/
      drwxr-xr-x. 3 root root  60 10月  3 23:25 .
      drwxrwxrwt. 3 root root  60 10月  3 23:25 ..
      drwxr-xr-x. 7 root root 200 10月  3 23:25 .git
      # 你會看到,這就是所謂的開發容器,在該目錄底下有個 .git 的隱藏目錄存在!
      
      # git init --bare prob
      Initialized empty Git repository in /dev/shm/prob/
      
      # ll -a prob
      drwxr-xr-x. 7 root root 200 10月  3 23:28 .
      drwxrwxrwt. 4 root root  80 10月  3 23:28 ..
      drwxr-xr-x. 2 root root  40 10月  3 23:28 branches
      -rw-r--r--. 1 root root  66 10月  3 23:28 config
      -rw-r--r--. 1 root root  73 10月  3 23:28 description
      -rw-r--r--. 1 root root  23 10月  3 23:28 HEAD
      drwxr-xr-x. 2 root root 260 10月  3 23:28 hooks
      drwxr-xr-x. 2 root root  60 10月  3 23:28 info
      drwxr-xr-x. 4 root root  80 10月  3 23:28 objects
      drwxr-xr-x. 4 root root  80 10月  3 23:28 refs
      # 這就是裸容器!你會發現到 prob/ 目錄內,幾乎就是 .git 的內容!所以不能直接在此開發!
      
    2. 如果你已經具有一個個人開發的開發容器了,可不可以分享出來成為裸容器呢? 可以的,也同樣是透過之前談到的 clone 即可!我們以之前玩弄許久的 /var/www/html 來做介紹:
      # cd /dev/shm
      # git clone --bare /var/www/html myhtml.git
      Cloning into bare repository 'myhtml.git'...
      done.
      
      # ll myhtml.git/
      drwxr-xr-x.  2 root root  40 10月  3 23:32 branches
      -rw-r--r--.  1 root root 105 10月  3 23:32 config
      -rw-r--r--.  1 root root  73 10月  3 23:32 description
      -rw-r--r--.  1 root root  23 10月  3 23:32 HEAD
      drwxr-xr-x.  2 root root 260 10月  3 23:32 hooks
      drwxr-xr-x.  2 root root  60 10月  3 23:32 info
      drwxr-xr-x. 49 root root 980 10月  3 23:32 objects
      -rw-r--r--.  1 root root 325 10月  3 23:32 packed-refs
      
      你會發現到, /dev/shm/myhtml.git 已經成為一個裸容器,可以提供許多人來共同參照、開發了。
    3. 一般命名:
      一般約定俗成的情境下,我們會將裸容器的目錄名稱取為 xxx.git,雖然是非必備,不過,既然目前大家都這樣設定, 那就依據網路的習慣來定義比較好!
  3. 建立權威容器所在的權限思考
    1. 建立者的身份:其實,使用 root 的身份來進行 git 的處理,是件很笨的事!因為,事後許多的資料處理都會出問題! 所以,建立者的身份最好是團隊內的一般帳號會比較好!否則,至少以 root 建立完成後,還是先處理權限問題, 然後在通知團隊成員較佳!否則可能經常會發生某些成員無法進行上傳/下載的困擾。
    2. 權限規劃:其實,最好是將共同開發者加入到同一個 Linux 群組內,這樣比較好規劃其權限。 如果某些限制導致某些帳號無法加入該群組,可能就得要用到 ACL 權限控制的行為了。
    3. 一個範例:
      共同群組: mypro
      使用者群: prousera, prouserb, prouserc
      
      額外用戶: student
      開發者目錄: /srv/myproject/
      
    4. 建立所需要的群組、帳號與密碼等資料
      # groupadd mypro
      # useradd -G mypro prousera
      # useradd -G mypro prouserb
      # useradd -G mypro prouserc
      # id prousera
      uid=1001(prousera) gid=1002(prousera) groups=1002(prousera),1001(mypro)
      
      # echo "prousera" | passwd --stdin prousera
      # echo "prouserb" | passwd --stdin prouserb
      # echo "prouserc" | passwd --stdin prouserc
      
      上面是一個很爛的範例,暫時將帳號與密碼設定為相同!事實上這是很不智的!我們目前是在測試, 請不要在公開的系統上面這樣做。
    5. 建立所需要的目錄與修改權限:
      # cd /srv
      # mkdir myproject
      # chgrp mypro myproject/
      # chmod 2775 myproject/
      # ll -d myproject/
      drwxrwsr-x. 2 root mypro 6 10月  3 23:51 myproject/
      
      # setfacl -m u:student:rwx myproject
      # setfacl -m d:u:student:rwx myproject
      # getfacl myproject/
      # file: myproject/
      # owner: root
      # group: mypro
      # flags: -s-
      user::rwx
      user:student:rwx
      group::rwx
      mask::rwx
      other::r-x
      default:user::rwx
      default:user:student:rwx
      default:group::rwx
      default:mask::rwx
      default:other::r-x
      
      這樣,在 /srv/myproject/ 目錄底下,你要如何新建不同的專案,都可以被接受了!
    6. 新建檔案的預設權限: umask
      另外,最好規範一下你的用戶,在操作系統前,先將 umask 切換到 002 較佳!
      # vim ~/.bashrc
      umask 002
      
      # source ~/.bashrc
      # umask
      0002
      
  4. 實際建立權威裸容器:
    1. 以 student 身份,以 /var/www/html 這個專案為來源,將權威容器放置於 /srv/myproject/roothtml.git 專案內
      # su - student
      $ cd /srv/myproject
      $ git clone --bare /var/www/html roothtml.git
      Cloning into bare repository 'roothtml.git'...
      done.
      
    2. 以 student 身份,建立空的裸容器,名為 /srv/myproject/roothtml2.git 專案
      $ cd /srv/myproject
      $ git init --bare roothtml2.git
      Initialized empty Git repository in /srv/myproject/roothtml2.git/
      
  5. 以權威容器建立自己本地的開發容器:
    1. student 之前已經建立過 ~/www 目錄,這個目錄比較舊,我們先將它刪除,然後以權威容器來直接建立 student 的開發容器:
      $ cd ~
      $ rm -rf www
      $ git clone /srv/myproject/roothtml.git/ www
      Cloning into 'www'...
      done.
      
      $ cd www
      $ git log
      commit 20a16c25d5876d0a9fcb034bffa4f63d92920bd2 (HEAD -> master, origin/master, origin/HEAD)
      Merge: 9b3ee0f aa8808e
      Author: VBird Tsai <dmtsai@mail.ksu.edu.tw>
      Date:   Sat Oct 3 07:56:23 2020 +0800
      
          Merge branch 'hw/unit2'
      .....
      
      很有趣的是,第一行最新的 commit 右側顯示了除了 HEAD 是 master 之外,也還有另外兩個名稱! 最重要的應該就是 origin/master 了!這就是遠端容器的名稱!
    2. 列出本地開發容器的遠端參照容器方式: git config --list
      $ git config --list
      user.name=VBird Tsai
      user.email=student@localhost
      core.repositoryformatversion=0
      core.filemode=true
      core.bare=false
      core.logallrefupdates=true
      remote.origin.url=/srv/myproject/roothtml.git/
      remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
      branch.master.remote=origin
      branch.master.merge=refs/heads/master
      
    3. 如果你想要在新建的新開發容器裡面增加 roothtml.git 專案,也可以這樣做:
      $ cd ~
      $ git init www2
      $ cd www2
      $ git remote add origin /srv/myproject/roothtml.git
      $ git remote update
      Fetching origin
      remote: Enumerating objects: 44, done.
      remote: Counting objects: 100% (44/44), done.
      remote: Compressing objects: 100% (33/33), done.
      remote: Total 44 (delta 14), reused 0 (delta 0)
      Unpacking objects: 100% (44/44), done.
      From /srv/myproject/roothtml
       * [new branch]      hw/unit1   -> origin/hw/unit1
       * [new branch]      hw/unit2   -> origin/hw/unit2
       * [new branch]      master     -> origin/master
       * [new tag]         V0.2       -> V0.2
      
      $ git branch -a
        remotes/origin/hw/unit1
        remotes/origin/hw/unit2
        remotes/origin/master
      
      這個 update 的功能,只是更新了 .git 的內容,因此,目前目錄下還是沒有任何資料!而使用了 git branch -a, 就可以列出來遠端 origin 的分支名稱了!現在,讓我們將遠端容器的 master 最新的資料抓下來!
      $ git pull origin master
      From /srv/myproject/roothtml
       * branch            master     -> FETCH_HEAD
      
      $ ll
      -rw-rw-r--. 1 student student 192 Oct  4 00:43 index.html
      -rw-rw-r--. 1 student student  21 Oct  4 00:43 phpinfo.php
      -rw-rw-r--. 1 student student 141 Oct  4 00:43 server.php
      drwxrwxr-x. 2 student student  24 Oct  4 00:43 unit1
      drwxrwxr-x. 2 student student  24 Oct  4 00:43 unit2
      
      $ git log
      ....
      
      這樣就將我們需要的檔案資料抓下來了!雖然步驟比較繁複,不過,可以更清楚整體流程的!
  6. 實做練習:
    1. 以 prousera 的身份,建立 git config --global user.email 與 user.name 的設定值!
    2. 以 prousera 的身份,建立權威裸容器,參照來源為 /var/www/html 目錄,且裸容器網址為 /srv/myproject/userahtml.git
    3. 以 prousera 的身份,建立開發容器,參照來源為 /srv/myproject/roothtml.git 目錄,且本地開發容器位於 ~/www。
    4. 以 prousera 的身份,先於 ~/www2 當中初始化 git 容器,然後設定遠端參照容器為 /srv/myproject/userahtml.git, 接下來請自行進行其他相關指令,最終 ~/www2 的內容會是 /srv/myproject/userahtml.git 的最新的 master 結構。

本地開發容器與遠端容器的推播

製作權威的容器目的就是在多人共用開發,但是我們無法直接在權威容器裡面進行送交的任務,反而需要透過 git pull, git push, git fetch 等指令進行資料的下載與上傳!那麼你應該要知道的是,權威容器、本地端容器、本地端暫存資料,以及本地端目錄的資料當中, 相關的指令關係如何分辨?其實簡單的說,可以參考底下的圖示 (圖示來源請參考文末的參考資料,感謝 Badu Huang):

前兩章的內容只在本地端,因此你可以知道,上圖左側的三個部份 (工作目錄、索引、本地端容器) 就是本地的開發容器, 因此我們只會用到 git add, git commit, git checkout 等。如果加上權威容器,那麼接下來的任務,就會加上 git push 與 git pull 等任務。 如果對於各個指令之間的相關性探討,可以參考文末高見龍先生撰寫的『 Git 面試題』相關說明。如果對於其他用過的指令相依性, 則可以參考下圖 (下圖來自 git wiki 說明)

上面兩張圖的方向剛剛好相反喔!參考的時候請注意個別的項目。基本上,你要知道的,就是工作目錄、本地端 .git 目錄、遠端權威容器目錄這三個關係即可。 Staget 或暫存,指的就是本地容器的類似快取 (你正在運作的資料與最近一次 commit 之間的差異)!

  1. 本地容器的開發行為:其實本地端開發跟之前的章節介紹的一模一樣,完全是 git commit 而已。 我們假設還需要增加第六堂課程的內容,所以這樣增加看看:
    $ whoami
    student
    
    $ cd ~/www
    $ mkdir unit6
    $ echo "i am unit 6" > unit6/index.php
    $ vim index.html
            <li><a href='unit6'>第六章作業</a></li>
    
    $ 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:   index.html
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    
            unit6/
    
    no changes added to commit (use "git add" and/or "git commit -a")
    
    $ git add -A
    $ git commit -a -m 'add unit 6 homework'
    [master c6f7f19] add unit 6 homework
     2 files changed, 2 insertions(+)
     create mode 100644 unit6/index.php
    
    $ git log
    commit c6f7f19498f37b15d1ab72369081762f608d237f (HEAD -> master)
    Author: VBird Tsai <student@localhost>
    Date:   Sun Oct 4 08:17:22 2020 +0800
    
        add unit 6 homework
    
    commit 20a16c25d5876d0a9fcb034bffa4f63d92920bd2 (origin/master, origin/HEAD)
    Merge: 9b3ee0f aa8808e
    Author: VBird Tsai <dmtsai@mail.ksu.edu.tw>
    Date:   Sat Oct 3 07:56:23 2020 +0800
    
        Merge branch 'hw/unit2'
    ....
    
    你可以清楚的發現到,遠端的 master (origin/master) 與本地端的 master 已經不一樣,看起來本地端的似乎比較新喔!
  2. 遠端容器的名稱定義: origin
    1. 一般來說,在 git 底下,預設的遠端容器名稱都會稱呼為 origin ,這個是預設值。基本上,也確實不太建議修改它。 所以,如果當你下達 git show-branch 時,就會出現這樣的結果:
      $ git show-branch -a
      * [master] add unit 6 homework
       ! [origin/HEAD] Merge branch 'hw/unit2'
        ! [origin/hw/unit1] add class1 homework
         ! [origin/hw/unit2] add one file
          ! [origin/master] Merge branch 'hw/unit2'
      -----
      *     [master] add unit 6 homework
      --  - [origin/HEAD] Merge branch 'hw/unit2'
      *+ ++ [origin/hw/unit2] add one file
      --  - [origin/HEAD^] add homework class1 to master
      *++ + [origin/hw/unit1] add class1 homework
      *+ ++ [origin/HEAD~2] add homework unit2
      *+ ++ [origin/HEAD~3] add homework unit2
      *++++ [origin/HEAD~4] add tmp/ ignore
      
      上面的訊息顯示各個分支,基本上,出現 origin 的,大部分就是在遠端容器上面的分支,其中 origin/HEAD 應該就是目前最新的。 本地端則是 master 為最新喔!
    2. 更改預設的 origin 名稱:基本上,不建議修改啦!不過,我們可以來玩一下!
      $ git remote rename origin server
      $ git show-branch -a
      * [master] add unit 6 homework
       ! [server/HEAD] Merge branch 'hw/unit2'
        ! [server/hw/unit1] add class1 homework
         ! [server/hw/unit2] add one file
          ! [server/master] Merge branch 'hw/unit2'
      -----
      ....
      
      $ git remote rename server origin
      # 記得最後請改回來!不要惡搞啊!哈哈哈!
      
  3. 開始將你變更的資料推播到 origin 這個權威容器中:
    $ git push
    Enumerating objects: 7, done.
    Counting objects: 100% (7/7), done.
    Delta compression using up to 2 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (5/5), 406 bytes | 406.00 KiB/s, done.
    Total 5 (delta 2), reused 0 (delta 0)
    To /srv/myproject/roothtml.git/
       20a16c2..c6f7f19  master -> master
    
    $ git log
    commit c6f7f19498f37b15d1ab72369081762f608d237f (HEAD -> master, origin/master, origin/HEAD)
    Author: VBird Tsai 
    Date:   Sun Oct 4 08:17:22 2020 +0800
    
        add unit 6 homework
    
    commit 20a16c25d5876d0a9fcb034bffa4f63d92920bd2
    Merge: 9b3ee0f aa8808e
    Author: VBird Tsai 
    Date:   Sat Oct 3 07:56:23 2020 +0800
    
        Merge branch 'hw/unit2'
    ....
    
    你可以發現,很快的,遠端容器與本地容器已經同步了!讓我們來確認一下本地端與權威容器的 flag 是否相同呢?
    $ git ls-remote
    From /srv/myproject/roothtml.git/
    c6f7f19498f37b15d1ab72369081762f608d237f        HEAD
    780235faf99eb3eff46d5b8b59f567f6eb5c80ea        refs/heads/hw/unit1
    aa8808ed0bb942376a0c4cfe8131a23e2bddd0bf        refs/heads/hw/unit2
    c6f7f19498f37b15d1ab72369081762f608d237f        refs/heads/master
    6c2760cb79bab3a279240bceb9d65e194e5678cc        refs/tags/V0.2
    1b0319971db9a8c2327a8283faf61498151dc0d6        refs/tags/V0.2^{}
    
    仔細比對一下,你會發現,遠端權威容器的 HEAD 確實已經跟我們的最新的 commit 相同 flag 喔!
  4. 實做練習:
    1. 事實上,我們前一個練習已經建立了 prousera 同步管理 /srv/myproject/roothtml.git 的任務,現在, 請以 prousera 的身份,進入到 ~/www 專案目錄內,然後觀察 origin 與本地端容器是否已經有差異了?
    2. 嘗試更新 (update) 參照資料,並同步更新 (pull) 。
    3. 嘗試做出第 7 章的作業資料,然後更新上傳到權威容器上面。
    4. 轉回 student 的身份,然後使用『 git log 』與『 git ls-remote 』看看 master 的 flag 是否相同?
    5. 使用 stutdent 的身份,進行 git remote update 以及 git pull 來處理同步的行為。
    6. 結論:當你要開發時,請務必記得查看本地端與遠端容器是否同步的問題喔!

分支的應用

事實上,權威容器也能夠有分支的!只是,有可能這些分支會被刪除,或者是額外新增!不論理由為何, 有時候分支的管理也是需要學一學的。老實說,分支與本地端容器的分支管理似乎也差不多!只是放置於權威容器就是了。

  1. 遠端分支的建立:

    建立遠端分支,可以先從本地端建立分支之後,加以上傳來處理即可。舉例來說,我們已經建立了第六課的作業, 但是卻發現到第四課作業在上次建立後被刪除了,因此,想要重建第四課的作業情境。應該這樣做:

    $ whoami
    student
    
    $ cd ~/www
    $ git log
    commit c6f7f19498f37b15d1ab72369081762f608d237f (HEAD -> master, origin/master, origin/HEAD)
    Author: VBird Tsai <student@localhost>
    Date:   Sun Oct 4 08:17:22 2020 +0800
    
        add unit 6 homework
    
    commit 20a16c25d5876d0a9fcb034bffa4f63d92920bd2
    Merge: 9b3ee0f aa8808e
    Author: VBird Tsai <dmtsai@mail.ksu.edu.tw>
    Date:   Sat Oct 3 07:56:23 2020 +0800
    
        Merge branch 'hw/unit2'
    .....
    
    $ git branch hw/unit4 20a16c
    $ git branch
      hw/unit4
    * master
    
    $ git checkout hw/unit4
    Switched to branch 'hw/unit4'
    
    $ git branch
    * hw/unit4
      master
    
    $ git push
    fatal: The current branch hw/unit4 has no upstream branch.
    To push the current branch and set the remote as upstream, use
    
        git push --set-upstream origin hw/unit4
    
    $ git push --set-upstream origin hw/unit4
    $ git ls-remote
    From /srv/myproject/roothtml.git/
    c6f7f19498f37b15d1ab72369081762f608d237f        HEAD
    780235faf99eb3eff46d5b8b59f567f6eb5c80ea        refs/heads/hw/unit1
    aa8808ed0bb942376a0c4cfe8131a23e2bddd0bf        refs/heads/hw/unit2
    20a16c25d5876d0a9fcb034bffa4f63d92920bd2        refs/heads/hw/unit4
    c6f7f19498f37b15d1ab72369081762f608d237f        refs/heads/master
    6c2760cb79bab3a279240bceb9d65e194e5678cc        refs/tags/V0.2
    1b0319971db9a8c2327a8283faf61498151dc0d6        refs/tags/V0.2^{}
    
    $ git checkout master
    
    這樣就處理好了分支的上傳!也就是說,你得要在本地端先進行分支建立後,才能上傳到權威裸容器上頭囉!
  2. 遠端容器分支的下載運作:

    假設 student 這個帳號只是建立了分支而已,他要他的同伴 prousera 繼續開發,因此,prousera 需要開始建立她自己本地端的 hw/unit4 這個分支才行。基本上,可以這樣做的:

    $ whoami
    prousera
    
    $ cd ~/www
    $ git remote update
    
    $ git branch -a
    * master
      remotes/origin/HEAD -> origin/master
      remotes/origin/hw/unit1
      remotes/origin/hw/unit2
      remotes/origin/hw/unit4
      remotes/origin/master
    
    $ git checkout hw/unit4
    Switched to branch 'hw/unit4'
    Your branch is up to date with 'origin/hw/unit4'.
    
    $ git log
    commit 20a16c25d5876d0a9fcb034bffa4f63d92920bd2 (HEAD -> hw/unit4, origin/hw/unit4)
    Merge: 9b3ee0f aa8808e
    Author: VBird Tsai <dmtsai@mail.ksu.edu.tw>
    Date:   Sat Oct 3 07:56:23 2020 +0800
    
        Merge branch 'hw/unit2'
    
    commit aa8808ed0bb942376a0c4cfe8131a23e2bddd0bf (origin/hw/unit2)
    Author: VBird Tsai <dmtsai@mail.ksu.edu.tw>
    Date:   Sat Oct 3 07:56:03 2020 +0800
    
        add one file
    ....
    
    看起來目前的狀況是正常的!沒啥大問題!確定轉到 hw/unit4 這個分支了!
  3. 分支處理完畢,上傳最新實做分支:

    跟前面的幾個範例一樣,假設這個分支是暫時的,用來將之前沒有完成的 bug 或作業完成,完成之後得要整合到 master 主線去, 所以,我們使用 prousera 繼續後面的實做流程看看:

    $ whoami
    prousera
    
    $ mkdir unit4
    $ echo "this is unit4" > unit4/index.php
    $ vim index.html
            <li><a href='unit4'>第四章作業</a></li>
    
    $ git add -A
    $ git commit -a -m 'add unit4 homework'
    [hw/unit4 28e67dd] add unit4 homework
     2 files changed, 2 insertions(+)
     create mode 100644 unit4/index.php
    
    $ git push origin hw/unit4
    
  4. 合併整合分支:

    繼續使用 prousera 的身份進行後續分支的合併行為看看:

    $ git checkout master
    Switched to branch 'master'
    Your branch is up to date with 'origin/master'.
    
    $ git merge hw/unit4
    Auto-merging index.html
    CONFLICT (content): Merge conflict in index.html
    Automatic merge failed; fix conflicts and then commit the result.
    
    $ vim index.html
            <li><a href='unit6'>第六章作業</a></li>
            <li><a href='unit4'>第四章作業</a></li>
            <li><a href='unit2'>第二章作業</a></li>
            <li><a href='unit1'>第一章作業</a></li>
    
    $ git commit -a -m 'merge unit4'
    [master 24f72c5] merge unit4
    
    $ git push
    Enumerating objects: 7, done.
    Counting objects: 100% (7/7), done.
    Delta compression using up to 2 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 343 bytes | 343.00 KiB/s, done.
    Total 3 (delta 2), reused 0 (delta 0)
    To /srv/myproject/roothtml.git
       c6f7f19..24f72c5  master -> master
    
    這樣就完成了一個遠端分支建立、分支開發與分支合併的動作了。
  5. 分支的刪除:

    分支建立完畢,我們的分支大部分都只是用來補充過去忘記實做的範本,現在,可以嘗試將不重要的分支刪除看看。 另外,我們假設這個專案的主要管理員是 student,因此,現在請切換到 student 進行後續的行為:

    $ whoami
    student
    
    $ cd ~/www
    $ git log
    commit c6f7f19498f37b15d1ab72369081762f608d237f (HEAD -> master, origin/master, origin/HEAD)
    Author: VBird Tsai <student@localhost>
    Date:   Sun Oct 4 08:17:22 2020 +0800
    
        add unit 6 homework
    ......
    
    $ git ls-remote
    From /srv/myproject/roothtml.git/
    24f72c570a22bb789181643c2897d702203166d7        HEAD
    780235faf99eb3eff46d5b8b59f567f6eb5c80ea        refs/heads/hw/unit1
    aa8808ed0bb942376a0c4cfe8131a23e2bddd0bf        refs/heads/hw/unit2
    28e67dd5545e37e1dabcd5c1438c7da9848006b5        refs/heads/hw/unit4
    24f72c570a22bb789181643c2897d702203166d7        refs/heads/master
    6c2760cb79bab3a279240bceb9d65e194e5678cc        refs/tags/V0.2
    1b0319971db9a8c2327a8283faf61498151dc0d6        refs/tags/V0.2^{}
    # 看起來就不像是最新的!所以,需要來更新一下!
    
    $ git remote update
    Fetching origin
    remote: Enumerating objects: 12, done.
    remote: Counting objects: 100% (12/12), done.
    remote: Compressing objects: 100% (6/6), done.
    remote: Total 8 (delta 4), reused 0 (delta 0)
    Unpacking objects: 100% (8/8), done.
    From /srv/myproject/roothtml
       c6f7f19..24f72c5  master     -> origin/master
       20a16c2..28e67dd  hw/unit4   -> origin/hw/unit4
    # git 報告說, origin/master 與 origin/hw/unit 都有更新過!
    
    $ git pull
    Updating c6f7f19..24f72c5
    Fast-forward
     index.html      | 1 +
     unit4/index.php | 1 +
     2 files changed, 2 insertions(+)
     create mode 100644 unit4/index.php
    
    $ git branch -a
      hw/unit4
    * master
      remotes/origin/HEAD -> origin/master
      remotes/origin/hw/unit1
      remotes/origin/hw/unit2
      remotes/origin/hw/unit4
      remotes/origin/master
    
    $ git branch -d hw/unit4
    Deleted branch hw/unit4 (was 20a16c2).
    
    $ git push origin --delete hw/unit4
    To /srv/myproject/roothtml.git/
     - [deleted]         hw/unit4
    
    $ git branch -a
    * master
      remotes/origin/HEAD -> origin/master
      remotes/origin/hw/unit1
      remotes/origin/hw/unit2
      remotes/origin/master
    
    刪除遠端的分支,也是需要透過 git push 才可以的!這樣就完成了相關的動作了!
  6. 實做練習:
    1. 繼續使用 student 的身份,刪除不重要的 hw/unit1, hw/unit2 等遠端容器的分支紀錄
    2. 用 student 的身份,啟用另一個新的分支,分支建立於目前的 master 分支上,作為第 10 章作業的分支系統
    3. 進入 hw/unit10 分支,然後建立分支作業,然後上傳到遠端容器上的新分支。處理完畢之後,讓 student 回歸到 master 分支即可停止。
    4. 假設新分支要給 prousera 確認無誤,因此,切換到 prousera 身份,然後下載 hw/unit10 分支, 並且換到該分支,看看有沒有問題,如果沒有問題,回到原本的 master 主線上,然後進行合併, 合併成功後,上傳到遠端容器上。
    5. student 更新到最新的版本上,然後刪除 hw/unit10 分支。

遠端伺服器

上面談到的遠端權威容器,大致上都還是在同一部系統上面的不同用戶間的處理,那如果每個用戶的系統都在不同機器上, 那就得要透過許多不同的協定來處理了。基本上,有 NFS 檔案系統協定、git 本身的協定、ssh 協定以及 http, https 協定等。 其中 http 與 https 大部分都是單向的,也就是只能從 server 上面抓下來 (git pull, git clone), 而 ssh 又似乎是最標準的協定,因此目前大部分的 git 內網通常直接使用 ssh 而已。

  1. 目前掌握的伺服器狀態:

    基本上,目前你掌握的兩部主機環境其實有點類似底下這樣:

       Internet ---WAN--- [ens3]  server  [ens7] ---LAN--- [ens3] client
    
    也就是說,Server 有兩張以上的界面,其中可以對外的,是 ens3 這個界面,而 client 的 ens3 必須要透過 server 的 ens7 才能夠上網。 現在,先讓我們處理一下 server 的環境。
  2. 伺服器的設定值:

    針對外部界面 ens3,我們外部界面使用的網路參數假定如下:

    • IP addr: 192.168.40.*/24
    • gateway: 192.168.40.254
    • DNS: 120.114.100.1, 168.95.1.1
    • hostname: gitserver*.ksu

    針對內部界面 ens7,我們使用的網路參數如下:

    • IP addr: 172.17.*.254/24
  3. 用戶端的設定值:
    • IP addr: 172.17.*.1/24
    • gateway: 172.17.*.254
    • DNS: 120.114.100.1, 168.95.1.1
    • hostname: gitclient*.ksu
  4. 伺服器端的防火牆狀態:
    1. 因為要提供用戶端使用 IP 偽裝,因此,在伺服器端的防火牆需要啟用 IP 偽裝的 NAT 伺服器設定,所以需要啟用內部與外部的領域 (zone):
      // 先來更改內 (internal) 與外 (external) 的領域對應的網路界面資訊
      # firewall-cmd --get-active-zones
      public
        interfaces: ens3 ens7
      
      # nmcli connection modify ens3 connection.zone external  # 對外
      # nmcli connection modify ens7 connection.zone internal  # 對內
      
      # firewall-cmd --get-active-zones
      external
        interfaces: ens3
      internal
        interfaces: ens7
      # 請注意一下, external 與 internal 是有意義的名稱,似乎不能亂調整喔!
      
    2. 開始啟用 IP 偽裝,意思是,所有從 ens3 出去的封包,全部偽裝成為 ens3 的 IP 之意。
      # firewall-cmd --zone=external --add-masquerade --permanent
      # firewall-cmd --reload
      
      這樣就搞定了!相當簡單!
  5. 用戶端 (gitclient*) 設定使用者帳號與相關連線機制:
    1. 假設 client 上面剛剛好也有 student 這個帳號,此帳號會引用 giserver 的資訊,亦即會用到 ssh://student@gitserver/srv/myproject/roothtml.git 的所在資料。那麼可以這樣做:
      $ whoami; hostname
      student
      gitclient200.ksu
      
      $ vim /etc/hosts
      172.17.200.254  gitserver200.ksu gitserver200 gitserver
      
      $ git clone ssh://student@gitserver/srv/myproject/roothtml.git www
      Cloning into 'www'...
      The authenticity of host 'gitserver (172.17.200.254)' can't be established.
      ECDSA key fingerprint is SHA256:waLgFyX/O5YwIlj939NQAnu16Wt5pM2mKVp8NFDGfCM.
      Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
      Warning: Permanently added 'gitserver,172.17.200.254' (ECDSA) to the list of known hosts.
      student@gitserver's password: <==這裡輸入密碼
      remote: Enumerating objects: 57, done.
      remote: Counting objects: 100% (57/57), done.
      remote: Compressing objects: 100% (42/42), done.
      remote: Total 57 (delta 20), reused 0 (delta 0)
      Receiving objects: 100% (57/57), 4.70 KiB | 4.70 MiB/s, done.
      Resolving deltas: 100% (20/20), done.
      
      $ git ls-remote
      student@gitserver's password: <==這裡輸入密碼
      From ssh://student@gitserver/srv/myproject/roothtml.git
      24f72c570a22bb789181643c2897d702203166d7        HEAD
      780235faf99eb3eff46d5b8b59f567f6eb5c80ea        refs/heads/hw/unit1
      aa8808ed0bb942376a0c4cfe8131a23e2bddd0bf        refs/heads/hw/unit2
      24f72c570a22bb789181643c2897d702203166d7        refs/heads/master
      6c2760cb79bab3a279240bceb9d65e194e5678cc        refs/tags/V0.2
      1b0319971db9a8c2327a8283faf61498151dc0d6        refs/tags/V0.2^{}
      
      $ git config -l
      core.repositoryformatversion=0
      core.filemode=true
      core.bare=false
      core.logallrefupdates=true
      remote.origin.url=ssh://student@gitserver/srv/myproject/roothtml.git
      remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
      branch.master.remote=origin
      branch.master.merge=refs/heads/master
      
      之後你就可以跟在本機上面進行所有動作一模一樣了!
    2. 增加 ssh 的金鑰:因為每次都要輸入密碼,操作起來非常不方便~所以,你可以自己增加自己的金鑰系統來處理這件事!
      $ ssh-keygen
      
      $ ssh-copy-id -i ~/.ssh/id_rsa.pub student@gitserver
      
      這樣,未來你在 www 的 git 目錄中,就可以直接連上伺服器操作了!就跟在本機系統工作一樣。
  6. 實做練習:
    1. 讓 gitclient 上面的 prouserb 這個帳號, clone 來自 gitserver 的 /srv/myproject/roothtml.git 的資料
    2. 讓 prouserb 操作 git 時,不需要輸入 ssh 密碼。

參考資料