at posts/single.html

Subversion ユーザーが Git を使ってみた (基本操作編)

この連休にhttps://www.amazon.co.jp/dp/4798023809を読みながら Git を使ってみた。

インストール

OS付属のパッケージツールを使ってインストールする。

$ sudo yum install git   # CentOS
$ sudo port install git-core  # Mac OS X
$ sudo apt-get install git-core   # Debian / Ubuntu

基本設定

まずはGit全般の設定(名前やメールアドレスの登録)から。

git config --global user.name MATSUOKA Kohei
git config --global user.email user@example.com  # 実際は自分のメールアドレスを入力

コンソールで色を付けるように設定。

git config --global color.diff auto
git config --global color.status auto
git config --global color.branch auto

設定した内容は $HOME/.gitconfig にテキストファイルで保存されている。直接編集してもOK。 git config -l コマンドでも内容を確認できる。

git config -l
user.name=MATSUOKA Kohei
user.email=user@example.com
color.diff=auto
color.status=auto
color.branch=auto

Git リポジトリの初期化

新規に作成する場合。

git init

別のリポジトリからコピーする場合。

git clone <コピー元リポジトリのURL>

Subversion での svnadmin create と svn co に相当する。 Subversion と違って作業中のディレクトリをいきなりバージョン管理対象に出来る気軽さは Mercurial と同じ。 (参考; 個人で簡単に使える分散バージョン管理ツール Mercurial

ファイルの編集からコミットまで

編集して add して commit するのは Subversion と同じ。 違うのは、 Subversion の add が最初の1回だけなのに対して、 Git は毎回 add が必要なこと。 これは、インデックスという概念が存在するから。

  • Subversionの場合 … ファイルを修正し、 svn commit でリポジトリに反映
  • Gitの場合 … ファイルを修正し、 git add でインデックスに登録し、 git commit でリポジトリに反映

インデックスは「コミット予約」もしくは「仮コミット」みたいなものと思っておけばOK。 ちなみに、インデックスに登録することを、「ステージする」と呼ぶ。

最初は面倒な仕組みに感じるけど、すぐに便利だと気がつく。 ローカルで編集したファイルを全てコミットするのなら、インデックスは不要。 でも、実際はそんなに単純じゃない。 メインの修正以外にもデバッグ用に他のファイルを修正したりする。

例えば、 a, b, c, d の4つのファイルを修正したとして、 a, b の2つのファイルしかコミットしたくない場合。 Subversion だとこうしてた。

svn diff a b   # 修正内容を確認
svn commit a b  # 修正内容をコミット

ここでうっかりファイル名を付けずに svn commit すると、意図せずに c, d までコミットしてしまう。 コミット対象以外のファイルを修正したことに気がつかずに、うっかり余計なファイルまでコミットしてしまった経験は誰もがあるはず。

Git ならこうする。

git diff  # 修正内容を確認(a, b, c, dすべての差分が表示される)
git add a b  # インデックスに登録
git diff --cached  # 修正内容を確認 (インデックスに登録したa, bの差分が表示される)
git commit  # 修正内容をコミット

インデックスを使うことで、コミットしたいファイルをより強く・安全に意識できる。 git add で明示的にファイルを指定しないとコミット対象にならないので、前述のようなミスが減る(ただし、 git commit -a を使えば Subversion と同じくいきなりコミットできる諸刃の剣)。 さらに、 git add -p を使うことで同じファイル内でも、特定の箇所だけをコミット対象としてインデックスに登録することもできる。 これは Subversion にはできないこと。

状況を確認する

履歴の確認は git log を使う。基本は Subversion と同じ感覚で使えるので省略。

現在の作業状況を確認するには、 git status コマンドを使う。 英語がたくさん出てくるけど、貴重な情報なのでビックリしないこと。

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.rb
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   misc/lib/compatible.rb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       .htaccess

読み方のコツを覚えれば怖くない。それぞれの状態ごとにファイルが出力されている。

  • Changes to be committed ... git addされてコミット待ちのファイル
  • Changed but not updated … ローカルで修正されているがaddされていないファイル
  • Untracked files … まだ管理対象に含まれていない(一度もコミットされていない)ファイル

さらに親切なことに、先に進める (add / commit) か元に戻す (reset / checkout) 方法が出力されている。これ重要。

ここまでのまとめ

Subversion 使いとしては、Gitのインデックスが混乱するので図にまとめてみた。 点線の矢印は、直前のコマンドを元に戻すためのコマンド。ファイルの移動ではないので注意。 Working Treeがローカルフォルダ。Indexがコミット予約された(ステージされた)ファイル置き場。Repositoryが履歴(コミット)を管理している場所。 ちなみに、IndexとRepositoryの実体は .git ディレクトリ内に存在する。

Git1

まとめと言いつつ、説明していない git reset HEAD^ が出てきているのはご愛敬。 git reset HEAD^ はコミットを取り消すためのコマンド。 HEAD^は最新の1つ前のバージョンを指す。 git reset HEAD^で1つ前の時点にリセットする(つまり、直前のコミットを取り消す)ことになる。 --softオプションを付けるとインデックスに登録された状態まで戻る。付けないとインデックスも前の時点に戻る(ローカルフォルダのみ変更後の状態になる)。

HEADは現在のコミット。HEAD^^は2つ前のコミット。 Subversion と違ってリビジョン番号は存在しない。 各リビジョンに相当する状態(コミット)を表す 「94ab9049acc65e15c640b5331c3f4db468d52f87」 のような文字列(ハッシュ値)は存在するけど、1つ前に戻すならHEAD^のほうが楽。 Subversion だと svnadmin コマンドを使わないとコミットを取り消すことはできないので、ちょっと驚き。

Subversion とのコマンド対比

SubversionGit
svn addgit add
svn commitgit add; git commit
svn diffgit diff; git diff --cached; git diff HEAD
svn revert <file>git checkout -- <file>
svn statusgit status
svn updategit pull
svn infogit remote show origin

svn update と svn info に対応するコマンドは、分散SCMである Git では他リポジトリとのやりとりに相当する。ここでは説明していないけど後で書くかも。

checkout/reset についてもう少し

さっきの図だけだとコマンドに一貫性が無いように思えるし、使いこなせる気がしない。 git statusと打てばヘルプが表示されるので実際は困らないけど、なんだか気持ち悪い。 なので、もう一つ図を書いてみた。

Git2

上から下に時間が流れる。 Working Tree, Index, Repositoryのそれぞれに対して、ファイルの変化 (A → A') がどのように伝搬するかを表している。

これを書いてようやく理解できた。

  • checkout -- <file> … Indexの内容を Working Tree に反映させる
    • git checkout HEAD -- <file> とした場合は、リポジトリの内容を Working Tree と Index に反映させる
  • reset … リポジトリの内容(過去バージョンを含む)をリポジトリのHEADと Index に書き戻す (--hardの場合はWorking Tree も書き戻す)
    • --soft の場合はリポジトリのHEADのみに書き戻す
    • --soft も --hard も指定しない場合はリポジトリのHEADと Index に書き戻す
    • --hard の場合はリポジトリのHEADと Index と Working Tree に書き戻す (すべて無かったことになる)

git add でインデックスに登録したファイル (A') を取り消すのに git reset HEAD を使うけど、実際には HEAD のファイル (A) をインデックスに書き戻していることになる。 git commit を取り消すために git reset HEAD^ を実行すると、1つ前の状態(HEAD^)を HEAD と Index に書き戻すしていることになる。 (さらに応用問題として、 git reset --soft HEAD を実行しても、実質的に何も起きないことも分かる)

ここに書いていないこと

  • 他リポジトリとの連携 (git pull, git push)
  • ブランチの扱い(ブランチを簡単に扱えるのが Subversion と比べての Git の大きな魅力)
  • GitHub の使い方

参考リンク

続きは Web で。

本当に Git を使おうと思うなら、最初に紹介したhttps://www.amazon.co.jp/dp/4798023809を手元に置いておくのが確実。 Git を使う上で悩むことのほとんどの答えが書いてあるから。 ボリュームは多いけど、十分に読む価値のある本。

個人的には GitHub を使う上で、8章(分散環境とブランチとの関連)、13章(リモートリポジトリ定義)、16章(間違いからの回復)がとても役に立った。

関連する日記