安裝octopress並佈署到github pages上面

昨天評估使用github.io存放網頁的技術,本來是想直接套用Jekyll的東西,無奈Markdown的支援度不如預期。後來想到Octopress應該支援度不差。估狗了一下的確可行,順手紀錄一下安裝方式。

先說結論,Octopress + Github pages的好處有

  • 使用git管理你的content
  • Octopress幫你搞定
    • deploy到github page
    • local PC preview,這樣可以改到爽再commit你的content
    • 支援Markdown,簡化寫網頁的effort
    • Open source,喜歡就自己改吧
    • code block有行數,歐耶。

可惜的是目前用起來有些排版醜醜的,先放著當備案吧。

資料有點雜,還是弄一下目錄好了


測試環境

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.2 LTS
Release:    14.04
Codename:   trusty

預先準備

github 帳號,以及開通git pages

安裝Octopress需要的軟體

$ sudo apt-get install ruby1.9.1-dev
$ sudo apt-get install nodejs
$ sudo apt-get install rbenv
$ sudo gem install bundler
$ rbenv rehash
$ bundle install

安裝Octopress

基本上就照手冊來,口頭來說就是

下載Octopress

官方網址如下

$ git clone https://github.com/imathis/octopress
$ cd octopress

安裝Octopress

$ rake install

指定你要deploy到Github page

跑下面的指令會要求你輸入Github Page的repository如
git@github.com:wen00072/wen00072.github.io.git

$ rake setup_github_pages

setup_github_pages很有趣,值得提一下他幫你做了什麼

  • 跟你討Github page的URL
  • 把你下載Octopress的repository remote名稱從origin改成octopress
  • 把你的Github page的repository設成目前git remote的origin
  • local新增source branch,從master切到該branch
  • 設定Octopress要deploy到你的Github pages
  • clone你的Github page的repository 到_deploy目錄

使用下面的指令,產生資料並上傳到你的Github page,此時直接連Github Page應該就會有網頁畫面了。

$ rake generate
$ rake deploy

再來我們直接把目前Octopress的Git repository和前面指令產生/修改新的檔案一口氣產生並加到你的Github page中的source branch吧。這個點特別有趣,可以仔細想想他為什麼這樣做。想通了應該對於git remote會有點感覺...吧?

你應該還記得前面rake setup_github_pages有提到Octopress會將你的Github page repository URL設成目前working directory git remote的orgin吧?

$ git add .
$ git commit -m "git add . after rake deploy"
$ git push origin source

所以現在你的Github page repository origin會有兩個branch

  • master
    • 網頁連過去看到的內容
    • 由rake deploy管控
  • source: 從Octopress拉下來的程式碼和你剛才指令新增的檔案
    • 你的content以及config由此更動

設定網頁資訊

官方網頁有提到如何更改設定。我只做最簡單的更動,就是_config.yml

diff --git a/_config.yml b/_config.yml
index 4bf56f1..e7de252 100644
--- a/_config.yml
+++ b/_config.yml
@@ -3,9 +3,9 @@
 # ----------------------- #
 
 url: http://wen00072.github.io
-title: My Octopress Blog
-subtitle: A blogging framework for hackers.
-author: Your Name
+title: 國王的耳朵是驢耳朵
+subtitle: 國王的耳朵是驢耳朵
+author: Wen Liao
 simple_search: https://www.google.com/search
 description:

如果要讓你的config馬上生效,除了local commit, push到source branch外,不要忘記套用到的Github page。

$ rake generate
$ rake deploy

撰寫及發表文章

撰寫文章

撰寫文章有兩種,一個是請Octopress幫你生一個範本,指令如下

$ rake new_post["你的標題"]

然後Octopress會幫你產生到source/_posts/下面
來個範例

$ rake new_post["First"]
mkdir -p source/_posts
Creating new post: source/_posts/2015-03-25-first.markdown

$ cat source/_posts/2015-03-25-first.markdown
---
layout: post
title: "First"
date: 2015-03-25 22:10:45 +0800
comments: true
categories: 
---

可以看到產生的檔案會有特定的規範。所以你也可以照樣造句,自己人肉寫一個,這就是第二種方式。不管怎樣,剩下的就是按照Markdown語法編輯檔案了。

發表文章

最開始有講過,使用Octopress的好處是可以改到爽再deploy到Github page,要如何做呢?很簡單

$ rake preview

接下來就是用瀏覽器連localhost:4000,如果不滿意就停止,開始改你的markdown檔案,再rake preview。改到爽為止,決定發佈就用下面的指令上傳到Github page上面。

$ rake deploy

上傳完畢後不要忘記local的markdown檔案並沒有commit並且更新。所以你可以做

$ git add source/_posts/
$ git commit -m "Hello world"
$ git push origin source

要注意git add source/_posts/第一次可以這樣做,接下來建議還是針對你的markdown檔案一個一個確認比較妥當。

這邊是我的示範網頁,短期內不打算更新。


注意事項

嘗試Octopress發現和以前使用的Markdown有一些要注意的地方

  • 第一個item (我用*) 前面一定要空一行,不然系統無法辨識
  • HTML 的anchor tag (我拿來做目錄跳躍)一定要<a name="ref"></a>,以前的<a name="ref"/>會讓Octopress的排版亂到讓你想殺人。

參考資料

Linux下單一帳號使用多個ssh-key

本篇文章假設你已經有一個ssh-key了

情境範例

遠端主機提供gitolite(一種git server)服務,提供server上有repo-software和repo-document兩個repository。平常你使用repo-software的rd@localhost的ssh-key開發專案。後來因故需要maintain 文件,所以需要新增doc@localhost的ssh-key去管理repo-document。

下面的情境假設你預設是rd@localhost的ssh-key,所以你要新增doc@localhost的ssh-key。

測試環境

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.2 LTS
Release:    14.04
Codename:   trusty

新增方式

  • 產生新的ssh-key
$ ssh-keygen -t rsa  -f 儲存key_檔名 -C "你的email"

範例

$ ssh-keygen -t rsa  -f doc_key_rsa -C "doc@localhost"
  • 搬到.ssh目錄 假設你存的檔名是~/doc_key_rsa
mv ~/doc_key_rsa* ~/.ssh
  • 修改.ssh/config 格式如下
# 這是註解 :p
Host ssh吃的主機名稱
    HostName 真正要連的主機名稱
    PreferredAuthentications publickey
    IdentityFile 儲存key_檔名

ssh吃的主機名稱真正要連的主機名稱可以不同。詳細的應用方式可以看參考資料。
來個範例

# 這是註解 :p
Host doc.remote_host
    HostName remote_host
    PreferredAuthentications publickey
    IdentityFile ~/.ssh/doc_key_rsa

驗證

透過ssh-add -l看看你加入的key已經在list了

$ ssh-add -l

接下來在請管理者把新的public key放入真正要連的主機的gitolite管理中心,然後連線的時候用ssh吃的主機名稱而不是真正要連的主機名稱應該就可以了。

參考資料

談談.git 目錄

如果有使用git的朋友,可能會知道git是一個分散式版本控制系統。這表示repository會放一份在本機上面。仔細或是有看書的人,會知道這些repository沒有意外會放在工作目錄的.git下面。今天無聊來這個目錄戳戳看。

因為愈寫愈多,還是弄一下目錄好了


測試環境

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.2 LTS
Release:    14.04
Codename:   trusty

$ git --version
git version 2.3.0

$ git remote show 
origin
$ git remote show origin 
* remote origin
  Fetch URL: https://github.com/pcman-bbs/pcmanx.git
  Push  URL: https://github.com/pcman-bbs/pcmanx.git
  HEAD branch: master
  Remote branches:
    gtk3                          tracked
    master                        tracked
    next-release                  tracked
    show_web_search_in_popup_menu tracked
    transparent_background        tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

.git目錄列表

/tmp/pcmanx$ ls -gG --group-directories-first .git
total 56
drwxrwxr-x 2  4096 Mar  3 23:36 branches
drwxrwxr-x 2  4096 Mar  3 23:36 hooks
drwxrwxr-x 2  4096 Mar  3 23:36 info
drwxrwxr-x 3  4096 Mar  3 23:37 logs
drwxrwxr-x 4  4096 Mar  3 23:36 objects
drwxrwxr-x 5  4096 Mar  3 23:37 refs
-rw-rw-r-- 1   264 Mar  3 23:37 config
-rw-rw-r-- 1    73 Mar  3 23:36 description
-rw-rw-r-- 1    23 Mar  3 23:37 HEAD
-rw-rw-r-- 1 12256 Mar  4 23:11 index
-rw-rw-r-- 1    41 Mar  4 23:11 ORIG_HEAD
-rw-rw-r-- 1   829 Mar  3 23:37 packed-refs

背景知識

這邊只列出需要的背景知識,可能有錯。

  • Git 的基本上資料有分blob, tree, commit, tag這四種型態
  • Git 的branch可以視為放一個檔案,這個檔案存放某個commit的HASH資訊

多說無益,直接操作,先來看這四種型態是三小

$ git log
commit fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
...

來看一下 fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304裏面是啥

$ git cat-file -p fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
tree 2f6db7ea7db6b66cb4c07a98e78b580f67872db6
parent 3fb419f40c10d67bd1b9b18152fb43cca58ca411
...

可以看到有放

  • tree型態的HASH
  • 上一個commit的HASH
  • commit的訊息

接下來來看tree吧

$ git cat-file -p 2f6db7ea7db6b66cb4c07a98e78b580f67872db6
...
100644 blob 5bc4b3f69d730535eb3a8aee28d7ef9273225d50    .gitignore
...
040000 tree b0b46378d75b1737257fef9d9eb433489b2d0ea0    build

有沒有和Linux的File system很像?目錄tree資料放的是inodeHASH值和檔案名稱的對應表。

我們可以看到HASH的型態有tree和blob,理所當然來看看blob吧。

$ git cat-file -p 5bc4b3f69d730535eb3a8aee28d7ef9273225d50
# autotools generated
Doxyfile
INSTALL
Makefile
Makefile.in
...

很明顯這是一個資料存放的地方。

然後我們來看branch是不是真的放在檔案,檔案內容是某個commit HASH吧,直接看範例。

$ git checkout -b br1
Switched to a new branch 'br1'
$ git checkout -b br2
Switched to a new branch 'br2'
$ tree .git/refs/
.git/refs/
├── heads
│   ├── br1
│   ├── br2
│   └── master
├── remotes
│   └── origin
│       └── HEAD
└── tags

$ git log
commit fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
...
$ cat .git/refs/heads/br1 
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
$ cat .git/refs/heads/br2
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
$ cat .git/refs/heads/master 
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304

.git第一層檔案

用file看一下,可以看到大部分都是純文字檔:

/tmp/pcmanx$ find .git -maxdepth 1 -type f -exec file {} \;
.git/index: Git index, version 2, 144 entries
.git/packed-refs: ASCII text
.git/config: ASCII text
.git/ORIG_HEAD: ASCII text
.git/description: ASCII text
.git/HEAD: ASCII text

接下來分別討論


index

先看下面的操作。

$ file .git/index 
.git/index: Git index, version 2, 144 entries

$ find -type f | grep -v .git\/ | cut -c 3- | sort | wc -l
144

$ git ls-files | wc -l
144

有興趣的人可以把| wc -l去掉玩看看,基本上這個是在描述repository的目錄結構。也就是說,哪個檔案要放在那個目錄。更精確的來說,協助那個blob hash要放在哪個tree中。根據這邊的說法,這個檔案可以協助

  • 提供產生tree object時需要的資料
  • 提供比對working directory和tree的資訊
  • merge產生衝突時,可以從index取得的資料協助解決衝突。不過這邊不是很懂,就單純字面翻譯了。

packed-refs

簡單來說,考量效率,git提供pack ref目錄的功能,ref的概念請看前面背景知識。

一樣,看例子比較快。

先用git show-ref看看我們有哪些reference

$ git show-ref
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/heads/master
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/remotes/origin/HEAD
c86b440bafe24caecfe0dbfbbd73031a1bcd1178 refs/remotes/origin/gtk3
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/remotes/origin/master
22da092eb00b78946fd70de24427d702a77b925b refs/remotes/origin/next-release
98ff18f84fad4686012bf6183734eeb1ec5d7f46 refs/remotes/origin/show_web_search_in_popup_menu
9542889d17d276321d6da2de8d1f9d95f76fb483 refs/remotes/origin/transparent_background
7a3629ab3da7c609cf3698319ab606ebae0998e7 refs/tags/0.3.2@221
f57350c72987263c2b745690bdc8013fbbd6067c refs/tags/0.3.3@243
522d1dc2568b5c7c256e05ca69412480e1afcfb3 refs/tags/0.3.4
8e9d9520125f64a0b894a5d575f0c2d98c3ec06b refs/tags/0.3.4@301
7031115a8ea1151efc1536b43a509774ca2c0777 refs/tags/0.3.7
3f4dcbe6e1aa0045fbeb9f1f761ce035e65defea refs/tags/1.1
098d158c8d8a7ce7c6b2c5c47c967c6137beced2 refs/tags/1.2

看看packed-refs裏面是三小?有沒有發現和上面很像啊?

$ cat .git/packed-refs 
# pack-refs with: peeled fully-peeled 
c86b440bafe24caecfe0dbfbbd73031a1bcd1178 refs/remotes/origin/gtk3
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/remotes/origin/master
22da092eb00b78946fd70de24427d702a77b925b refs/remotes/origin/next-release
98ff18f84fad4686012bf6183734eeb1ec5d7f46 refs/remotes/origin/show_web_search_in_popup_menu
9542889d17d276321d6da2de8d1f9d95f76fb483 refs/remotes/origin/transparent_background
7a3629ab3da7c609cf3698319ab606ebae0998e7 refs/tags/0.3.2@221
f57350c72987263c2b745690bdc8013fbbd6067c refs/tags/0.3.3@243
522d1dc2568b5c7c256e05ca69412480e1afcfb3 refs/tags/0.3.4
8e9d9520125f64a0b894a5d575f0c2d98c3ec06b refs/tags/0.3.4@301
7031115a8ea1151efc1536b43a509774ca2c0777 refs/tags/0.3.7
3f4dcbe6e1aa0045fbeb9f1f761ce035e65defea refs/tags/1.1
098d158c8d8a7ce7c6b2c5c47c967c6137beced2 refs/tags/1.2

看一下ref目錄,上面的那些reference並不存在在.git/ref裏面

$ tree .git/refs/
.git/refs/
├── heads
│   └── master
├── remotes
│   └── origin
│       └── HEAD
└── tags

4 directories, 2 files

好,生一個branch看看。

$ git checkout -b I_got_a_new_ref
Switched to a new branch 'I_got_a_new_ref'

喔喔!.git/refs出現了剛才產生的ref

$ tree .git/refs/
.git/refs/
├── heads
│   ├── I_got_a_new_ref
│   └── master
├── remotes
│   └── origin
│       └── HEAD
└── tags

4 directories, 3 files

跑一下git gc整理一下

$ git gc
Counting objects: 5002, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (1068/1068), done.
Writing objects: 100% (5002/5002), done.
Total 5002 (delta 3913), reused 5002 (delta 3913)

疑?剛才的branch不見了。

$ tree .git/refs/
.git/refs/
├── heads
├── remotes
│   └── origin
│       └── HEAD
└── tags

4 directories, 1 file

跑去pack-ref了

$ cat .git/packed-refs 
# pack-refs with: peeled fully-peeled 
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/heads/I_got_a_new_ref
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/heads/master
c86b440bafe24caecfe0dbfbbd73031a1bcd1178 refs/remotes/origin/gtk3
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/remotes/origin/master
22da092eb00b78946fd70de24427d702a77b925b refs/remotes/origin/next-release
98ff18f84fad4686012bf6183734eeb1ec5d7f46 refs/remotes/origin/show_web_search_in_popup_menu
9542889d17d276321d6da2de8d1f9d95f76fb483 refs/remotes/origin/transparent_background
7a3629ab3da7c609cf3698319ab606ebae0998e7 refs/tags/0.3.2@221
f57350c72987263c2b745690bdc8013fbbd6067c refs/tags/0.3.3@243
522d1dc2568b5c7c256e05ca69412480e1afcfb3 refs/tags/0.3.4
8e9d9520125f64a0b894a5d575f0c2d98c3ec06b refs/tags/0.3.4@301
7031115a8ea1151efc1536b43a509774ca2c0777 refs/tags/0.3.7
3f4dcbe6e1aa0045fbeb9f1f761ce035e65defea refs/tags/1.1
098d158c8d8a7ce7c6b2c5c47c967c6137beced2 refs/tags/1.2

config

其實這個是個大哉問,就值得專門開一個章節討論。所以我整理到這個頁面,請自行前往閱讀。


ORIG_HEAD

這個檔案很有趣,一開始其實不存在。後來不知道怎麼突然跑出來,問了估狗和男人(man git reset)後得到的答案就是當reset的時候會把舊的HEAD hash放到這邊。為什麼要這樣做呢,當然是反悔用的。你可以man git commitORIG_HEAD就可以看看git commit --amend同效果的範例了。


description

懶得解釋,跳過


HEAD

望文生義,當然是指向目前所在的commit。

等等!

指向目前所在的commit是三小?
一樣,來看例子。

目前clone下來,裏面放的是一個檔案,檔案裏面是什麼呢?一樣是個commit HASH

$ cat .git/HEAD 
ref: refs/heads/master

$ cat .git/refs/heads/master
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304

$ git cat-file -t fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
commit

那我直接checkout一個不是branch的可以嘛?當然沒問題。
可以看到git會把你checkout的commit HASH寫入.git/HEAD

$ strace git checkout 93f0addca5b2723ff0f1b744b032f359
...
open("/tmp/pcmanx/.git/HEAD.lock", O_RDWR|O_CREAT|O_EXCL, 0666) = 3
write(3, "93f0addca5b2723ff0f1b744b032f359"..., 40) = 40
write(3, "\n", 1)                       = 1
close(3)                                = 0
...
rename("/tmp/pcmanx/.git/HEAD.lock", "/tmp/pcmanx/.git/HEAD") = 0
...

$ cat .git/HEAD 
93f0addca5b2723ff0f1b744b032f359938a286d

第一層目錄


branches

快過期的東西(出處),跳過。


hooks

hook,這個翻成中文很鳥,保留原文。基本上就是一組callback,特定的時候會去呼叫。我目前生意沒有做很大,有需要再去看。列出預設的檔案如下

$ ll .git/hooks/
total 48
drwxrwxr-x 2 wen wen 4096 Mar  9 23:10 ./
drwxrwxr-x 8 wen wen 4096 Mar  9 23:21 ../
-rwxrwxr-x 1 wen wen  452 Mar  9 23:10 applypatch-msg.sample*
-rwxrwxr-x 1 wen wen  896 Mar  9 23:10 commit-msg.sample*
-rwxrwxr-x 1 wen wen  189 Mar  9 23:10 post-update.sample*
-rwxrwxr-x 1 wen wen  398 Mar  9 23:10 pre-applypatch.sample*
-rwxrwxr-x 1 wen wen 1642 Mar  9 23:10 pre-commit.sample*
-rwxrwxr-x 1 wen wen 1239 Mar  9 23:10 prepare-commit-msg.sample*
-rwxrwxr-x 1 wen wen 1348 Mar  9 23:10 pre-push.sample*
-rwxrwxr-x 1 wen wen 4898 Mar  9 23:10 pre-rebase.sample*
-rwxrwxr-x 1 wen wen 3611 Mar  9 23:10 update.sample*

info

儲存資訊,廢話。目前裏面只有一個exclude,而且檔案內容還都是註解。但是這邊有說還有其他檔案。另外值得提的是exclude說明有提到.gitignore針對的是git status, git rm, git add, git clean。其他的操作還是得在.git/log/exclude中設定。


logs

這是存放log的地方。log是什麼呢?直接看個例子。

我們先新增一個測試檔案

$ touch test2
$ git add test2
$ git commit test2 -m "test2"
[master 09580e4] test2
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test2

執行git reflog,可以看到你剛才的動作以及有當次HEAD的commit HASH已經被記起來。

$ git reflog 
09580e4 HEAD@{0}: commit: test2
fd5d6ab HEAD@{1}: checkout: moving from 93f0addca5b2723ff0f1b744b032f359938a286d to master
93f0add HEAD@{2}: checkout: moving from master to 93f0addca5b2723ff0f1b744b032f359938a286d
...

這東西有什麼用處呢?當然是讓你反悔用的。再來看個例子

首先開一個branch

$ git checkout -b demo_reflog
Switched to a new branch 'demo_reflog'

新增並commit file1

$ touch file1
$ git add file1
$ git commit file1 -m "file 1"
[demo_reflog c982c70] file 1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file1

然後新增並commit file2

$ touch file2
$ git add file2
$ git commit file2 -m "file 2"
[demo_reflog b85f4d4] file 2
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file2

切回master並且宰掉剛才的branch

$ git checkout master 
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

$ git branch -D demo_reflog  
Deleted branch demo_reflog (was b85f4d4).

假設上一個動作是手滑誤刪,能不能反悔。
YES YOU CAN!

先看看log

$ git reflog 
fd5d6ab HEAD@{0}: checkout: moving from demo_reflog to master
b85f4d4 HEAD@{1}: commit: file 2
c982c70 HEAD@{2}: commit: file 1
...

可以知道上次branch最後commit的HASH,那麼我們就可以切過去然後開另外一個branch了。

$ git checkout -b i_am_back b85f4d4
Switched to a new branch 'i_am_back'

可以看到資料回來了。

$ git log --pretty=oneline --abbrev-commit
b85f4d4 file 2
c982c70 file 1
fd5d6ab Fix invisible cairo caret issue.
...

當然這還是險招,git本身保留是有保存期限的。有興趣問男人。man git gc

不知道有沒有會問:「啊不是要介紹.git/logs嘛?」

問得好!猜猜剛才git reflogs檔案從哪裡讀出來?

一樣請出strace大大

$ strace -e open git reflog 
...
open("/tmp/pcmanx/.git/logs/HEAD", O_RDONLY) = 3
...

有看到.git/logs/HEAD吧?有沒有想看一下裏面是啥?至少我想看。

$ cat .git/logs/HEAD
...
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 c982c70f00875b83e4845ca35ef0ca4c70f73e64 Wen.Liao <censored> 1425997414 +0800    commit: file 1
c982c70f00875b83e4845ca35ef0ca4c70f73e64 b85f4d4dca4012a2d1785750061fae9e03e817e1 Wen.Liao <censored> 1425997438 +0800    commit: file 2
b85f4d4dca4012a2d1785750061fae9e03e817e1 fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 Wen.Liao <censored> 1425997673 +0800    checkout: moving from demo_reflog to master
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 b85f4d4dca4012a2d1785750061fae9e03e817e1 Wen.Liao <censored> 1425997732 +0800    checkout: moving from master to b85f4d4
b85f4d4dca4012a2d1785750061fae9e03e817e1 b85f4d4dca4012a2d1785750061fae9e03e817e1 Wen.Liao <censored> 1425997746 +0800    checkout: moving from b85f4d4dca4012a2d1785750061fae9e03e817e1 to i_am_back

精確的來說,.git/logs下面不只紀錄HEAD的log,還有各種ref的log。有興趣的朋友可以自行參考這邊,並且進去目錄逛逛。


objects

先看剛clone下來的目錄結構

$ tree .git/objects/
.git/objects/
├── info
└── pack
    ├── pack-6042320de285747900cc3263a47f7bab429cabf8.idx
    └── pack-6042320de285747900cc3263a47f7bab429cabf8.pack

來生一個目錄和並且commit一個東西進去。

$ mkdir tree
$ echo test > tree/file
$ git add tree/file 
$ git commit tree/file -m "test tree and file"
[master 2ba01ef] test tree and file
 1 file changed, 1 insertion(+)
 create mode 100644 tree/file

然後看看這個目錄變化吧

$ tree .git/objects/
.git/objects/
├── 2b
│   └── a01ef5583f35dd57b5fb5bde732f73bc315b6d
├── 9d
│   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
├── de
│   └── f69c2c316574aaf328e546486fa750eb9c53a0
├── e1
│   └── b8ecbb1f19709f3a4867a0ffe08bb2e07acf19
├── info
└── pack
    ├── pack-6042320de285747900cc3263a47f7bab429cabf8.idx
    └── pack-6042320de285747900cc3263a47f7bab429cabf8.pack

6 directories, 6 files

接下來把目錄和檔案混起來,用git cat-file -p看看裏面是啥

$ git cat-file -p 2ba01ef5583f35dd57b5fb5bde732f73bc315b6d
tree def69c2c316574aaf328e546486fa750eb9c53a0
parent fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
author Wen.Liao <censored> 1425999641 +0800
committer Wen.Liao <censored> 1425999641 +0800

test tree and file
$ git cat-file -p 9daeafb9864cf43055ae93beb0afd6c7d144bfa4
test
$ git cat-file -p def69c2c316574aaf328e546486fa750eb9c53a0
100644 blob 5bc4b3f69d730535eb3a8aee28d7ef9273225d50    .gitignore
...
040000 tree e1b8ecbb1f19709f3a4867a0ffe08bb2e07acf19    tree
$ git cat-file -p e1b8ecbb1f19709f3a4867a0ffe08bb2e07acf19
100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4    file

其實不難猜,你local commit的東西會被處理後變成object,並且依照hash分門別類地存放。

接下來我們來做個測試

$ git gc
Counting objects: 5006, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (1070/1070), done.
Writing objects: 100% (5006/5006), done.
Total 5006 (delta 3914), reused 5001 (delta 3913)

可以看到,剛才的目錄全部消失,而pack目錄的檔案名稱也和前面的不同了。對照上面git gc的訊息,可以知道git gc做了壓縮,並且將這些object一起壓到pack目錄的檔案中了。

$ tree .git/objects/
.git/objects/
├── info
│   └── packs
└── pack
    ├── pack-57d163604c430e1919a18e97c5b1312291b62721.idx
    └── pack-57d163604c430e1919a18e97c5b1312291b62721.pack

2 directories, 3 files

所以我們可以從觀察中做出結論。Git object的存放有兩種型式

  • loose object:就是剛才看到用[HASH前兩碼]/[剩下HASH] 的目錄檔案結構
  • packed object: 把object壓縮成兩個檔案

而looose object型式經過git gc後會有可能轉換成packed object,反之亦然。詳細規則問男人。
由於git gc是用來清除整理local repository,也就是說做了git gc是有可能刪除用不到的資料。詳細情況一樣請問男人man git gc


refs


參考資料

ProGit: Customizing Git - Git Configuration導讀

目錄


測試環境

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.2 LTS
Release:    14.04
Codename:   trusty

$ git --version
git version 2.3.0

$ git remote -v
origin  https://github.com/pcman-bbs/pcmanx.git (fetch)
origin  https://github.com/pcman-bbs/pcmanx.git (push)

簡易使用方式

最簡單的使用方式

  • 新增/更改選項
    • git config 型態 名稱 你要設定的值
      • 常見的型態有--system或是--global
  • 讀取選項
    • git config --get 名稱

值得注意的是,git config 的名稱一般來說會使用.作為分類的參考如
git config test.user.name.first Liao
git config test.user.name.last Wen
而在設定檔中,會用[]把第一個.的前面的字串放在前面,然後最後一個.後面的字串和你要設定的值變成

  • 最後一個.後面的字串=你要設定的值

你應該要問,那中間的呢?好問題,中間的就放在[]裏面囉。

很模糊?看例子比較快。

$ git config test.user.name.first Liao
$ git config test.user.name.last Wen
$ cat .git/config
...
[test "user.name"]
    first = Liao
    last = Wen

git config參考檔案以及順序

要注意的是/etc/gitconfig不一定存在,我的是沒有。所以生一個測試驗證用。

  • /etc/gitconfig
    • git config --system產生
  • ~/.gitconfig
    • git config --global產生
  • 你的local repostiory/.git/config
    • 你的local repostiory下面執行git config產生

檔案讀取順序是由上往下,全部都有資料的話。最後回傳值順序是由下而上。
要練習驗證,所以來看一下。

用strace看git config --get user.name開檔案的順序。

$ strace -e open git config --get user.name
...
open("/etc/gitconfig", O_RDONLY)        = 3
open("/home/user/.gitconfig", O_RDONLY)  = 3
open(".git/config", O_RDONLY)           = 3
...
Wen.Liao
+++ exited with 0 +++

可以看到/etc/gitcnfig沒有user.name

$ cat /etc/gitconfig  | grep name

~/.gitconfig下有Wen.Liao的字串

$ cat ~/.gitconfig 
[user]
    name = Wen.Liao
...

同樣的,目前repository下面的config是沒有user.name資料

cat .git/config  | grep name

接下來改一下user.name

$ strace -e open git config user.name qqq
...
open("/etc/gitconfig", O_RDONLY)        = 3
open("/home/user/.gitconfig", O_RDONLY)  = 3
open(".git/config", O_RDONLY)           = 3
...
+++ exited with 0 +++

可以看到目前目錄下.git/config新增了user.name

$ cat .git/config 
...
[user]
    name = qqq

驗證是否.git/config的值真的會比~/.gitconfig優先

$ git config --get user.name
qqq

git config 選項節錄

Git config 選項有分為server端和client端,大部分選項和client有關,文件中節錄部份,我再節錄我有興趣的部份。想要知道全部的選項可以問男人。man git config。再次提醒,如果是你要套用到全部git repository請用git config --global,不然這些選項只會在你目前git repository生效。

  • core.editor
    • 你要commit 時候會用的編輯器
  • commit.template
    • 這個比較重要,如果你要套用commit template,就用這個。把你自己或是團隊的commit template引入吧。我自己會用# 為template加入註解。
  • color.ui
    • 為你的UI文字上色。可以設成true 或是false

指定使用外部比較命令

git可以指令使用外部比較命令,首先要搞清楚git 會送什麼參數給外部命令,所以我們先寫個script印出參數,並且指定外部diff的時候使用這個script。

$ cat ~/bin/git_diff.sh 
#!/bin/sh
echo $@

$ git config --global diff.external ~/bin/git_diff.sh

$ git diff HEAD^
src/core/caret.cpp /tmp/6nZQoO_caret.cpp 99fdca2d1d2b90e6452555572d3b0c3cf3ae75b0 100644 src/core/caret.cpp 15b662547eb5a853f02a6fc086e07feed2e45d4f 100644

這些參數的順序是
1 更動的檔案名稱
2 將被更動前的檔案內容另存新檔路徑
3 被更動前的檔案blob hash
4 被更動前檔案權限
5 將被更動後的檔案內容另存新檔路徑
6 被更動後的檔案blob hash
7 被更動後檔案權限

對我們來說,有興趣的是2和5,所以我們可以將命令更動為

$ cat ~/bin/git_diff.sh 
#!/bin/sh -x
echo $@
meld $2 $5

meld是一個GUI程式的比對工具。接下來就來測試吧

$ git diff HEAD^
+ echo src/core/caret.cpp /tmp/0M6Fgi_caret.cpp 99fdca2d1d2b90e6452555572d3b0c3cf3ae75b0 100644 src/core/caret.cpp 15b662547eb5a853f02a6fc086e07feed2e45d4f 100644
src/core/caret.cpp /tmp/0M6Fgi_caret.cpp 99fdca2d1d2b90e6452555572d3b0c3cf3ae75b0 100644 src/core/caret.cpp 15b662547eb5a853f02a6fc086e07feed2e45d4f 100644
+ meld /tmp/0M6Fgi_caret.cpp src/core/caret.cpp

GUI畫面如下:

手冊上面還有提到merge tools 的設定,本人不感興趣。跳過。


參考資料

談談git remote

在軟體專案開發常常會需要比對不同的repository。舉例來說,你可能在遠端有一套軟體專案,這個專案是從upstream fork 下來,那麼如果要把upstream 新的功能合併到專案,人肉合併往往是最容易出錯又最沒效率的方式。如果這兩個專案都有git,那麼git remote就是你的救星。針對剛才講的更詳細的use case可以看Git Hub的範例

直接拿Use case範例,不囉唆。
Demo情境

  • 有兩個遠端repositories: repo1, repo2
  • 將repo1 clone下來到local repository,另外一個透過git remote加入到local repository
  • 在local建立一個branch,對應到repo2
  • merge repo1到repo2
    • 要注意的是其實以upstream 情況是剛好相反,一般來說repo1,也就是你clone的才是你要開發的專案。選擇這樣的case單純只是吃飽撐著。

詳細行為範例如下


建立測試環境

產生兩個遠端repositories: repo1, repo2

repo1

$ mkdir remote_repo1
$ cd remote_repo1/
$ git init .
Initialized empty Git repository in /tmp/remote_repo1/.git/
$ echo "repo1" > myfile
$ git add myfile 
$ git commit myfile -m "init"
[master (root-commit) 043d3c6] init
 1 file changed, 1 insertion(+)
 create mode 100644 myfile

repo2

$ cd ../
$ mkdir remote_repo2
$ cd remote_repo2/
$ git init .
Initialized empty Git repository in /tmp/remote_repo2/.git/
$ echo "repo2" > myfile
$ git add myfile 
$ git commit myfile -m "init"
[master (root-commit) 5c329bf] init
 1 file changed, 1 insertion(+)
 create mode 100644 myfile

看一下目錄架構

$ cd ../
$ tree remote_repo1 remote_repo2/
remote_repo1
└── myfile
remote_repo2/
└── myfile

新增remote URL

就 git clone,沒啥好說

$ git clone remote_repo1/ local_repo
Cloning into 'local_repo'...
done.
$ cd local_repo/
$ git remote -v
origin  /tmp/remote_repo1/ (fetch)
origin  /tmp/remote_repo1/ (push)

建立對應remote URL的local branch

我們用了git remote add 本地如何稱呼remote remote的URL

$ git remote add repo2 /tmp/remote_repo2/
$ git remote -v
origin  /tmp/remote_repo1/ (fetch)
origin  /tmp/remote_repo1/ (push)
repo2   /tmp/remote_repo2/ (fetch)
repo2   /tmp/remote_repo2/ (push)

接下來把remote URL的repository 拉下來

$ git fetch repo2
warning: no common commits
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /tmp/remote_repo2
 * [new branch]      master     -> repo2/master

然後建立一個branch,對應到repo2的master

$ git checkout -b local_repo2 repo2/master
Branch local_repo2 set up to track remote branch master from repo2.
Switched to a new branch 'local_repo2'

$ git branch -a
* local_repo2
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/repo2/master

$ git diff master 
diff --git a/myfile b/myfile
index 464d9cd..27d38ae 100644
--- a/myfile
+++ b/myfile
@@ -1 +1 @@
-repo1
+repo2

在local合併兩個Romote Branch

剩下非常直覺,就把兩個local branch merge,整理完衝突收工。

$ git merge master 
Auto-merging myfile
CONFLICT (add/add): Merge conflict in myfile
Automatic merge failed; fix conflicts and then commit the result.

$ git status 
On branch local_repo2
Your branch is up-to-date with 'repo2/master'.
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both added:      myfile

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

$ cat myfile 
<<<<<<< HEAD
repo2
=======
repo1
>>>>>>> master
$ echo shut-up > myfile 

$ git add myfile 

$ git commit -m "Overwrite myfile"
[local_repo2 a76c0a6] Overwrite myfile

請注意這個case不能夠把更動push 回repo2/master 會噴錯誤如下

$ git push repo2 
warning: push.default is unset; its implicit value has changed in
Git 2.0 from 'matching' to 'simple'. To squelch this message
and maintain the traditional behavior, use:

  git config --global push.default matching

To squelch this message and adopt the new behavior now, use:

  git config --global push.default simple

When push.default is set to 'matching', git will push local branches
to the remote branches that already exist with the same name.

Since Git 2.0, Git defaults to the more conservative 'simple'
behavior, which only pushes the current branch to the corresponding
remote branch that 'git pull' uses to update the current branch.

See 'git help config' and search for 'push.default' for further information.
(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode
'current' instead of 'simple' if you sometimes use older versions of Git)

fatal: The upstream branch of your current branch does not match
the name of your current branch.  To push to the upstream branch
on the remote, use

    git push repo2 HEAD:master

To push to the branch of the same name on the remote, use

    git push repo2 local_repo2

To choose either option permanently, see push.default in 'git help config'.

簡單翻譯一下,目前local branch名稱在repo2上面沒有,你要嘛

  • 指定push到repo2的master: git push repo2 HEAD:master
  • 要嘛指定push local branch到repo2 repository上

第一個情況需要bared repository,時間關係有空再談。


參考資料

島唄 - The boom

這是一首反省二戰日本政府在沖繩島的決策造成大量當地人冤死悲劇的歌曲。

在二次大戰末期,米軍開始準備入侵日本本島,因此需要佔領沖繩作為前進基地。當時的日本政府在當地強徵年輕男女學生在戰場上做雜務以及醫療協助。同時大力宣傳米軍的邪惡形象,宣稱米軍殘暴邪惡,表示與其被米軍俘虜生不如死,不如自殺比較痛快。網路上可以看到日本政府會發手榴彈給當地居民的資料。加上日本政府強力徵收糧食,估計戰役結束後平民死亡人數預估超過十萬人(出處)。

回到主題,這首歌是1992年出版的。根據Wikipedia的說明,主唱者去沖繩的時候,參觀了姬百合和平祈念資料館,了解這片土地上發生的事情,以及館內倖存者親口說出的故事後,感到非常惆悵。當他離開姬百合和平祈念資料館的時候,映入眼前的是一整片的甘蔗田,讓他動起創作的想法。這就是The boom出版島唄(shima-uta)這首歌的由來。

我不會日文,不過ptt有中文歌詞翻譯的歌詞,也有詳細的背景說明。希望人類記取這些慘痛的教訓,不要再放任不負責任的政府亂搞,最後導致悲劇發生。

參考資料

Ubuntu 14.04 下面的VirtualBox存取USB設備

照慣例,列一下測試環境

  • Host OS

    $ lsb_release -a
    No LSB modules are available.
    Distributor ID: Ubuntu
    Description:    Ubuntu 14.04.2 LTS
    Release:    14.04
    Codename:   trusty
    
  • Guest OS
    同上。

  • VirtualBox 版本: Oracle VM VirtualBox Manager 4.3.22


主要有兩件事要做,就是


將使用者加入Virtual Box 群組

把使用者加入vboxusers群組

$ sudo usermod -a -G vboxusers $USER

Double check是否已經在vboxusers group 指令

$ groups $USER

安裝延伸套件(Optional)

$ sudo virtualbox Oracle_VM_VirtualBox_Extension_Pack-4.3.22-98236.vbox-extpack
  • 從主選單中要使用USB VM的Settings->USB選單中,勾選Enable USB 2.0 (EHCI) Controller,請確認VM是Powered off狀態。
  • 驗證:
    • Start VM
    • VM視窗中選Devices -> USB -> 挑你想要使用的USB
    • Guest OS開完機,Linux的話用可以使用lsusb驗證

參考資料

系統函式庫的debug 資訊放在那邊?

在查詢hello world中的_start呼叫__libc_start_main部份,使用到了反組譯工具。觀察反組譯的部份發現有可能需要看一下libc本身的__libc_start_main組合語言的行為。以前的經驗,這種情況先拉有debug 資訊的套件來看,所以拉了libc6-dbg下來,結果下來的結果,完全無法反組譯。後來請教網友Scott Tasi才發現我錯很大。

先講結論:

  • libc的debug 資訊和本身的binary完全隔開。所以反組譯要看的仍然是在原本的libc.so這個檔案。

年紀大了才發現能夠從結論中問問題收穫會更多,所以我就來問

  • 問題一:誰來用這些debug 資訊?
  • 問題二:既然debug 資訊不在binary內?那麼怎麼找到額外的debug 資訊?

先來回答誰來用這些debug 資訊?其實這個根本就是廢話,gdb用心酸的啊。其實這只是在鋪梗,這表示找第二個問題的答案就會和gdb有很大的關係,也就是說我們可以把問題縮小到和gdb相關 (註一)

好,我們來重複問題二

  • 既然debug 資訊不在binary內?那麼怎麼找到額外的debug 資訊?

從常理來猜測,顯然是原本的binary有地方告訴gdb「有額外的除錯檔案,請你載入的時候去那邊抓除錯資訊」。我們直接看GDB手冊怎麼說

  • gdb允許把debug 資訊放在binary外面,而gdb可以透過兩種方式取得
    • 執行的binary內提供link讓gdb可以摸到有debug 資訊的檔案。如果是執行檔,可能就稱為執行檔.debug
      • 這個link資料,放兩個東西
        • 不包含directory的檔案名稱
        • 該檔案名稱算出的CRC 碼
    • 透過build ID,因為和我找的無關,跳過。我們focus在第一個方式。

有了debug link資訊後,仍然有幾個細節需要釐清

  • 問題三:debug 資訊檔案放在哪個目錄?
  • 問題四:debug link放在binary 的哪裡?

一樣來看手冊。手冊上說debug 資訊檔案會依以下的順序搜尋

  • 該執行檔的存放目錄
  • 該執行檔的存放目錄下面同樣目錄名稱,但是加上.debug。如/usr/bin -> /usr/bin.debug/
  • 系統預設的debug 目錄

以上是問題三的的解答,剩下問題四我就認為可以把整個故事說完。所以來看問題四吧。

  • 問題四:debug link放在binary 的哪裡?

同樣的在手冊中有提到,binary 中有特別的section稱為.gnu_debuglink,就是存放debug 資訊檔案的資訊。存放的資訊為

  • 存放debug 資訊的檔案名稱
  • padding for 4-byte alignment
  • 4 byte CRC checksum

好啦,有了完整的故事,當然要看是不是在唬爛。我們來看libc.so吧

$ pwd
/lib/arm-linux-gnueabi
$ objdump -h libc-2.19.so | grep debug
 68 .gnu_debuglink 00000014  00000000  00000000  001336a0  2**0
$ objdump -s -j .gnu_debuglink libc-2.19.so 

libc-2.19.so:     file format elf32-littlearm

Contents of section .gnu_debuglink:
 0000 6c696263 2d322e31 392e736f 00000000  libc-2.19.so....
 0010 1b8f248c                             ..$. 
 
$ sudo apt-get install libc6-dbg
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
  libc6-dbg
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 3,290 kB of archives.
After this operation, 18.1 MB of additional disk space will be used.
Get:1 http://不告訴你/debian/ jessie/main libc6-dbg armel 2.19-13 [3,290 kB]
Fetched 3,290 kB in 4s (701 kB/s)      
Selecting previously unselected package libc6-dbg:armel.
(Reading database ... 46987 files and directories currently installed.)
Preparing to unpack .../libc6-dbg_2.19-13_armel.deb ...
Unpacking libc6-dbg:armel (2.19-13) ...
Setting up libc6-dbg:armel (2.19-13) ...
$ find /usr/lib/debug/ |grep libc-2
/usr/lib/debug/lib/arm-linux-gnueabi/libc-2.19.so

那麼這個和我原本要反組譯libc的結果有什麼關係?嘛,旅行的精華就是在迷路不是嗎?


測試環境

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 8.0 (jessie)
Release:    8.0
Codename:   jessie

$ uname -a
Linux debian 3.2.0-4-versatile #1 Debian 3.2.65-1+deb7u1 armv5tejl GNU/Linux

$ gcc -dumpmachine
arm-linux-gnueabi

參考資料


註解


一:精確來說,這應該不是只有gdb才能用,我不過偷懶而已。