はじめに
実際に git を使って、どうなったかを書いていきます。対象のプロジェクトは、「Windows Emulator」の WINE です。
今回は、ローカルマシンでのソース修正とビルド&テストの繰り返し時に必要な事柄に対象を絞ります。
実際にやってみた体験記として書いていきますので、もっと良いやり方がある可能性もあります。また、一部には、実験から推定した結果もメモ代わりに書いておきますので、実際にやってみると少し結果が違っている物もあるかもしれないことお断りしておきます。ただし、原則的には実験結果に基づいています。
関連リンク
[Wine Emulator の内部構造 : Inside Wine] (https://qiita.com/Yutaka_Aoki/items/9d38ea98406f4096435b)
前提
日時: 2018/02/25
OS: Linux(Ubuntu 12系)
WINEのソースのバージョン: WINE 3.1
カレントディレクトリ : ~ 即ち、/home/my_name
バイナリのWINE: 既にバイナリの WINE がインストール済みで、
~/.wine
が出来ているとする。この .wine は、WINE の設定が入っているフォルダ。
結論から先に
以下、次のように略す :
Working Tree WT
スナップショット snap
ブランチ名 ブラ名
01. git commit -a # 現在のブランチにsnapを保存する。
02. git branch ブラ名 # 新しいブランチを作成する。
03. git branch -D ブラ名 # 指定したブランチを削除
04. git branch -m ブラ名 # 現在いるブランチのブランチ名を修正
05. git branch -m 旧ブラ名 新ブラ名 # ブランチ名の修正
06. git checkout ブラ名 # ブランチの最後のsnapを WT に復帰する。
07. git checkout ブラ名^ # ブランチの最後の1つ前のsnapを WT に復帰する。
08. git checkout ブラ名^5 # ブランチの最後から5つ前のsnapを WT に復帰する。
09. git checkout HEAD # 現在のブランチの最後のsnapを WT に復帰する。
10. git checkout HEAD^ # 現在のブランチの1つ前のsnapを WT に復帰する。
11. git checkout HEAD^5 # 現在のブランチの5つ前のsnapを WT に復帰する。
12. git diff # 現在のブランチの最後のsnap(=HEAD)を WT と比較する。
13. git diff HEAD^5 # 現在のブランチの5つ前のsnapを WT と比較する。
14. git diff ブラ名 # 指定したブランチの最後のsnapとWTを比較する。
15. git diff ブラ名^ # 指定したブランチの1つ古い版のsnapとWTを比較する。
16. git diff ブラ名^5 # 指定したブランチの5つ古い版のsnapとWTを比較する。
17. git diff ブラ名1 ブラ名2 # 2つのブランチの最後のsnapを比較する。
18. git diff ブラ名1 ブラ名2 x.c # 2つのブランチの最後のsnapのファイルx.cを比較する。
19. git diff -w # タブ、空白、改行の違いを無視して比較する。
20. git add xxx.c # xxx.c を今後ずっと追跡(Tracking)するように git に依頼する。
21. git branch # 現存する branch と現在いる branch の場所を表示する。
22. git log # snapの履歴を表示する。
23. git status # 重要なのは、Trackingされてないファイルを表示する機能。
感覚的には、commit が「save」、checkout が「load」。
HEAD は、スナップショットの「アドレス(場所)」が入っているポインタ。
HEAD^^^ と、HEAD^3 は同じ意味。
HEAD = &snap_shot_5;
の時、感覚的には、
HEAD^ == HEAD->m_pPrev
HEAD^^ == HEAD->m_pPrev->m_pPrev
HEAD^^^ == HEAD->m_pPrev->m_pPrev->m_pPrev
HEAD^3 == HEAD->m_pPrev->m_pPrev->m_pPrev
のような感じ。
0.2 Working Tree
HDD の中に実際に格納され、エディタやコンパイラなどが直接見えるようになっているファイルとディレクトリ構造の全体。
0.3 スナップショット
Working Tree のファイルの中で、追跡されているファイル群をディレクトリ構造を含めてまとめて保存したもの。
ファイル xxx.c を追跡するようにするのは、
git add xxx.c
で行う。
0.4 ブランチ
ソースを枝分かれさせて、保存するための枝。
通常、「Tree 構造」というのは、中身は「節や葉」に入ると考えることが多い。しかし、git におけるブランチには、直線的にスナップが入るので、節か葉というよりも、長さが変わり得るところの「枝」という感覚はあっているかもしれない。
WINE の場合は、git clone した直後、「master」という名称のブランチがただ 1 つだけ存在していた。
0.5 ブランチの中にスナップショット
ブランチの中にスナップショットが沢山入る。
そしてそれは、1次元配列のように直線的に並ぶような感じ。
流れ
1. ローカルにダウンロードする。
$ git clone git://source.winehq.org/git/wine.git
この時点で、
~/wine
というディレクトリが出来て、(分かりやすくて重要なものとしては)以下のようなフォルダ構成が出来る:
~/wine/dlls
~/wine/dlls/kernel32 # kernel32.dll.so(5.5MB) を作成
~/wine/dlls/user32 # user32.dll.so(4.3MB) を作成
~/wine/dlls/gdi32 # gdi32.dll.so(2.7MB) を作成
~/wine/dlls/winex11.drv # Linux 用の部分, winex11.drv.so(1.8MB) を作成
~/wine/loader #
~/wine/libs
~/wine/libs/wine # loader.c, libwine.so.1.0(1.9MB) を作成
~/wine/server # wineserver(2.8MB) を作成
~/wine/tools
~/wine/tools/sfnt2fon
~/wine/tools/widl
~/wine/tools/winapi
~/wine/tools/winebuild # winebuild (245.8KB) を作る
~/wine/tools/winedump
~/wine/tools/winegcc # winegcc(71.2KB), wineg++(71.2KB), winecpp(71.2KB) を作る
~/wine/tools/winemaker # winemaker(Perlスクリプト, 95.1kb)
~/wine/tools/wmc
~/wine/tools/wrc
~/wine/programs
~/wine/programs/attrib
~/wine/programs/cmd # cmd.exe
~/wine/programs/control
~/wine/programs/eject
~/wine/programs/explorer
~/wine/programs/hh # chm help viewer
~/wine/programs/iexplorer
~/wine/programs/ipconfig
~/wine/programs/netstat
~/wine/programs/notepad # notepad
~/wine/programs/ping
~/wine/programs/regedit # レジストリエディタ
~/wine/programs/rundll32
~/wine/programs/shutdown
~/wine/programs/start
~/wine/programs/subst
~/wine/programs/svchost
~/wine/programs/taskkill
~/wine/programs/tasklist
~/wine/programs/taskmgr
~/wine/programs/uninstaller
~/wine/programs/wineboot
~/wine/programs/winebroeser
~/wine/programs/winecfg # winecfg
~/wine/programs/wineconsole # wineconsole
~/wine/programs/winedbg # winedbg
~/wine/programs/winefile
~/wine/programs/winemenubuilder
~/wine/programs/winepath # winepath
~/wine/programs/wordpad
~/wine/programs/xcopy # xcopy
~/wine/programs/・・・
~/wine/include
~/wine/fonts
~/wine/documentation
~/wine/.git # Wineのソースとは直接関係ないので触れない方が良い。
1.2. .wine と wine
.wine と wine は別物で、
~/.wine # wine の設定ファイル
~/wine # wine のソース
となっている。
1.3. カレントディレクトリを wine 以下に移す。
$ cd wine
として、カレントディレクトリを wine に移す。
そうしないと、git コマンドが、どのプロジェクトに対する指令かを判別できない。
なお、wine 以下の、サブディレクトリに入るのは構わない。
2. 自分の名前とメールアドレスを登録する。
$ git config --global user.name "Taro Yamada"
$ git config --global user.email "ABC12345@nifty.com"
などとしておく。
3. 試しにソースを見てみる。
~/wine/dlls/gdi32/painting.c に以下のような関数が定義されている。
これは、Win32 の LineTo() API である :
/***********************************************************************
* LineTo (GDI32.@)
*/
BOOL WINAPI LineTo( HDC hdc, INT x, INT y )
{
DC * dc = get_dc_ptr( hdc );
PHYSDEV physdev;
BOOL ret;
TRACE( "%p, (%d, %d)\n", hdc, x, y );
if(!dc) return FALSE;
update_dc( dc );
physdev = GET_DC_PHYSDEV( dc, pLineTo );
ret = physdev->funcs->pLineTo( physdev, x, y );
if(ret)
{
dc->cur_pos.x = x;
dc->cur_pos.y = y;
}
release_dc_ptr( dc );
return ret;
}
4. 初期ビルドしてみる。
$ ./configure
$ make -j 10
$ sudo make -j 10 install
5. 起動してみる。
$ wineserver -k # 今起動している wine サーバーを終了させる
$ wine notepad
6. 日本語フォントの不具合対策
日本語フォントで不具合が出たなら。
$ sudo apt-get remove wine
$ sudo apt-get install wine
とする。
7. 独自修正の準備のための最初のビルド
$ ./configure --prefix=~/winbin2
$ make -j 10
$ make -j 10 install
8. 起動してみる。
$ wineserver -k # 今起動している wine サーバーを終了させる
$ wine notepad
9. ここまでが全て成功するまで努力する。
何か不具合が出たら、直るまで頑張ってください。
10. ソースを修正してみる。
~/wine/dlls/
の中のどこかの関数に、
FIXME( "YA, Kitade---" );
と入れてみる。
$ cd wine/dlls/user32
$ make
$ cd ../../
$ make -j 10 install
11. パスを通す。
ここで、パスを通しておく。通し方は後で書く。
12. テストする。
$ wineserver -k
$ wine notepad
端末に、
YA, Kitade---
と出ることを確認する。
ここまでで、git の話をする前提が整ったことになります。
さて、ここから、git の本題
g1. 現在のソース全体をスナップショットとして、残しておく。
$ git commit -a
すると、デフォルトのエディタが起動する。
エディタの内容は、# でのコメントが大量に入った状態のテキストになっている。
先頭の行でカーソルがブリンクしているので、その辺りに、何か意味のある文章を書いておく。
CTRL+O で保存して、CTRL+X で、エディタを終了する。
このコマンドでは、あまり大きなファイルコピーなどがなされないので、処理は短時間で終わる。
g2. 確認する。
スナップショットの履歴を一覧として表示してみよう:
$ git log
g3. 繰り返し作業。
ソースを修正し、ビルド & テストし、上手く行った場合は、定期的にg1 の git commit -a を繰り返す。
g4. 直前のスナップショットと、現在のソース( *.c, *.h )類とを比較する。
$ git diff
g10. 修正を一度に沢山入れてみたら思わぬ不具合が出た場合
修正を入れる前の上手く行っていたソース全体を A とする。
修正を入れた後のソース全体を B とする。
一つの手段としては、A から B へと少しずつ修正していって、ビルド & テストを繰り返し、
上手く行かない修正がどれであるかを見極める、という方法がある。
そのための方法を書いておく。
g10.1. ダメなソース全体もいったん、スナップショットとして保存したい。
色々やり方があるが、バックアップを取らないと壊れる可能性も考えて、まず、ダメなソースを、すぐにスナップショットとして記録しておきたい。そこで、ダメな方を今まで通りのブランチに保存して、良いソースを、新しいブランチ「antei」の中へと作っていくことにする。
逆に、ダメなソースのスナップショットを、新しく作ったブランチの中に保存する方法もあるので、それは、次節の「g11」で触れることにする。
g10.2. 駄目ソース全体「B」のスナップショットを入れる。
$ git commit -a
とします。この時の作業は、g1 と同じです。
これで、B のソース全体が、HEAD という名前のスナップショットに、A のソース全体は、HEAD^ という名前のスナップショットになります。
g10.3. A と B を比較します。
A と WT との比較でも良いので、
$ git diff HEAD^
でも可能ですが、
$ git diff HEAD^ HEAD
としても構いません。diff より後の 2つの引数には、どのスナップショットを比較するかを指定する名前を指定し、一般形は、
$ git diff 名前1 名前2
のようです。名前にブランチ名そのものを指定した場合、そのブランチにおける最新のスナップショットが比較対象になります。
「HEAD^」などと書いた場合、現在のブランチの中で、最後スナップショットより1つ前のスナップショットを指す事になります、古いスナップショットの指定の仕方は、
HEAD^ # 1つ前
HEAD^^ # 2つ前
HEAD^^^ # 3つ前
HEAD^10 # 10個前
となります。「HEAD」というのは、現在のブランチを表すポインタのようなもので実際に H,E,A,D という4文字を書きます。
また、HEAD の部分は、代わりにブランチ名を書くこともできます。その場合、例えば、ブランチ名^ は、そのブランチの最後より1つ前のスナップショットを意味します。以下、同様です。
なお、今の場合には当てはまりませんが、もし比較した2つのスナップショットに差が無い場合は何も表示されません。
パラメータが1つの場合、例えば :
$ git diff ブランチ名
とすれば、現在のワーキング領域(ディスク上にある *.c や *.h など)と指定したブランチの最新のスナップショットとの比較になります。git comit -a した直後なら、
$ git diff HEAD
は、何も出ません。それは、git commit -a が、ワーキング領域にある *.c や *.h のスナップショットを現在のブランチの中に新規に作り、かつ、HEAD ポインタがその最新のスナップショットに移るためです。
さて、話を戻して、比較結果は、端末内に出ます。これは、less コマンドと同様の方式です。カーソルキーや、スクロールキーなどが使えます。終了は、小文字で、q キーを押します。
g10.4. 差のある場所を、ファイルに保存してじっくりとエディタで見たい。
g10.3 だと、画面に表示されてしまうので、じっくり見るのは難しいです。その場合、差のある場所を、いったんファイルに保存してから、好きなエディタで見ると良いです。例えばこうします :
$ git diff HEAD^ >a
$ gedit a
自分の場合は、
$ git diff HEAD^ >a
$ wz a
としています。前提として、bash 起動時に次のように設定しています :
・・・
alias wz='~/my-wz.sh'
・・・
Wz エディタ起動用のスクリプトを置いておきます :
aaa=
if [ $# -ne 0 ]; then
aaa=`winepath -w $1`
fi
wine '~/.wine/drive_c/Program Files/WZ EDITOR/wzeditor.exe' $aaa
g10.5. Working Tree を問題が無かった(古い)バージョンのソースに戻す。
それは、1つ前のスナップショットに戻してやれば良いのです。これは、
$ git checkout HEAD^
とします。次のようなメッセージが出ます。
Note: checking out 'HEAD^'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at 1234567... スナップショットの表題テキスト
現状確認のため、
$ git branch
とすると、
* (no branch)
master
と出ると思います。これは現在、特殊な状態にあることを示しています。
でも大丈夫です。
g10.6. 「antei」ブランチを作る。
$ git branch antei
とすると、antei という名前のブランチが出来ます。
$ git branch
とすると、
* (no branch)
master
antei
と出ると思います。
g10.12. antei ブランチへ移動
$ git checkout antei
とします。
$ git branch
とすると、
master
* antei
と出ると思います。
g10.13. A と B の中の xxx.c のファイルの比較
$ git diff master xxx.c
とした場合、「-」が、master の B、「+」が、working tree の antei の 「A」
として表示されます。
または、
$ git diff antei master xxx.c
とした場合、「-」が、antei の A、「+」が、master の 「B」
として表示されます。
g11. g10 の別解。逆に、dame ブランチを作る。
g10 では、master の最後にダメなスナップショット B を保存した上で、後から、安定なスナップショットを入れるための antei ブランチを作りました。その手順でも良いのですが、途中、長いメッセージが出たり、ブランチとして、「(no branch)」なる状態が現れたりしてしまいました。
ならば、逆の方法を考えましょう。
g11.1. 予想される手順
- dame ブランチを作る。
- そこに移る。
- そこに駄目ソース B のスナップショットを入れる。
- 元の master ブランチに戻る。
これは、今のところ実験して見ませんので、予想されるコマンドだけ書いておきます。
$ git branch dame #1
$ git checkout dame #2
$ git commit -a #3
$ git checkout master #4
g11.2. A と B のソースの比較 :
$ git diff master dame
↑だと、Aが「-」として、Bが「+」として表示されます。
$ git diff dame
↑だと、dame が「-」として、 working tree が「+」として表示されるので、結局、
Bが「-」として、Aが「+」として表示されます。
どっちでも構いません。
g11.3. A と B の中の xxx.c のファイルの比較
$ git diff master dame xxx.c
または、
$ git diff dame xxx.c
g15. Working Tree のファイルと、直近のスナップショットとの状態比較
$ git status
とすると良い。
今まで、(取り消したとか言う話は別として)一度でも、git add されているファイルは、「Tracking」状態と表示される。毎回毎回 git add する必要は全くない。
逆に、(取り消したとか言う話は別として)一度も、git add されていないファイルは、「Untracking」状態である。
g16. git add の意味は「これからずっと追跡してください」
いろいろな記事で、まるで原則的には毎回 git add してから、git commit しなくてはならないような事が書かれているが、それは、誤った理解である。
実際には、
git add aaa.c
は、git に、「これからずっと aaa.c を追跡してください」という事をお願いするコマンドである。
だから、一度実行すれば、取り消したような場合は別として、同じファイルに対しては二度と実行する必要は無いはず。
そして、スナップショットを保存したいときは、ほぼ必ず、
$ git commit -a
とするのが基本で、「-a」を付けない、という事はまず考えなくて良いらしい。
実際、このコマンドは「Untracking状態」のファイルは、全く commit しない。つまり、スナップショットに残さない。
だから、「-a」オプションを付けても、スナップショットに残したくないファイルに付いては残されることはないから安心して、「-a」オプションを付けてよい、と思える。
g17. git status、git log、git branch の表示の違い
1. git status
2. git log
3. git branch
- は、直近のスナップショットと Working Tree を比較するコマンド。
非 Tracking 状態のファイルも全て(長くなる場合は適切に省略されて)表示される。また、
その日付から、ファイルの更新具合も分かる。 - は、Snapshotを残した (git commit) 履歴を表示するコマンド。
- は、branch の様子を表示するコマンド。現在どの branch にいるかも分かる。
g18. 空白をタブに変更したり、改行位置を変更してしまったソースを比較
$ git diff
ではなく、
$ git diff -w
とすると良いです。空白や改行の違いは、無視してくれるので、タブか空白かの違いも無視てくれます。
g19. 過去のスナップショットに移動していくためのコマンド
実験的に導いた結論は、これをするためのコマンドは、
$ git checkout 名前
です。名前の部分には、次のようなものが来ます。
HEAD
HEAD^
HEAD^^
HEAD^5
ブランチ名
ブランチ名^
ブランチ名^^
ブランチ名^5
別のブランチに移動したい場合は、
$ git checkout ブランチ名
ですが、この場合、Working Tree が、そのブランチの最後のスナップショットに移ります。
つまり、HDD の、/wine/ 以下の *.c, *.h などが、ブランチの最後のスナップショットのものに
置き換わります。移動する前には aaa.c があったが、移動後のスナップショットでは
aaa.c が無かった場合には、aaa.c は、「現実の」 HDD の /wine/ 以下からは、削除されます。
逆に、ファイルが増える場合もあります。
なお、置き換えが起きるのは、*.c, *.h などと書きましたが、これは厳密には、「追跡対象に指定していたファイル」が該当します。追跡に付いては、g16. を見てください。
さらに、このブランチの一つ前のスナップショットに移りたい場合は、
$ git checkout HEAD^
とします。これらは、まとめて、
$ git checkout ブランチ名
$ git checkout HEAD^
と書くことも出来ますが、さらに、一度に、
$ git checkout ブランチ名^
と書くこともできます。どちらでも同じですが、このようにすると、Working Tree の *.c, *.h を、あるブランチの最新から1つ古いスナップショットへと切り替えることができます。
g20. 感覚的には、commit と checkout が逆の働きをする。
N88-BASIC 的な感覚では、commit が「save」。checkout が「load」という感じになります。
branch は、「directory」や「folder」の概念と少し似たところがありますが、結構違います。
g30. 一番、書きたかったこと。
git は、過去のスナップショットに巻き戻したとき、追跡対象になっている *.c や *.h に関しては、Working Tree の同名ファイルを、古いバージョンに戻してくれる。
しかし、Makefile は、ファイルの日付の新旧に基づいて、gcc で実際にコンパイルを行うかどうかを決定している。
だから、この状態で、
$ make
としても、.o より、.c の方が古いために、コンパイル作業が全く発生しない事がある。
これは困った現象である。
対策としては、make clean としてしまうことがある。しかし、これでは、全コンパイルがかかってしまう。
これを避けたいなら、.o や 実行ファイル(ELF)、.so までを git の追跡対象にしてしまう手が考えられる。
つまり、git add を *.o や、ELF ファイルに対しても行ってしまうのである。
実際にどうなるかは試してない。
この場合、古いスナップショットに戻ると、.o や 実行ファイル、.so までが、古い日付の物に巻き戻されるようになるはずである。
ただし、git がそういう事を前提に設計されているかどうかは分からないので試してみると不具合が起きるかもしれない。