Dropbox で Git のワークツリーのみを同期する
Dropbox に置いたディレクトリを Git でバージョン管理しつつ,リポジトリを同期対象から外し,ワークツリーのみを同期する方法を考えてみました。 Git と Dropbox の連携というと,Dropbox をプライベートなリモートリポジトリとして利用する方法が知られていますが,この記事ではそれとは別の連携方法を試しています。
Git と Dropbox の連携
やっていること
Dropbox 内のディレクトリをリポジトリとして初期化する際に,リポジトリの管理データが格納される Git ディレクトリの場所を Dropbox の外に指定します。
git 1.7.5 で追加された --separate-git-dir
オプションを使うと Git ディレクトリの場所を指定することができます。
作業を中断して別の端末で続きをするときには,Dropbox で同期した作業ディレクトリの内容を維持するために git pull
ではなく git fetch
+ git reset
を使用します。
できるようになること
現在の作業を中断して別の端末で続きをしなくてはならない状況で,一時的なコミットを作らずにワークツリーの状態を別のリポジトリにコピーすることができます。 また,移動中や出先など Git が使えない環境にいる場合やコミットしていない更新がある場合でも最新の作業内容にアクセスすることができるようになります。 GitHub はブラウザから編集する機能がありますが,当然コミット&プッシュしていないと編集できません。
Dropbox 内のディレクトリを単純に Git で管理した場合に生じるいくつかの問題が解消されます。 たとえば,同期中にコミットしてリポジトリを破壊してしまう危険性はありませんし,Git で何か操作をするたびに余計な更新が発生することもありません。 Dropbox の無料ユーザーであるわたしにとっては,リポジトリのデータでストレージ容量が圧迫されるのを防ぐことができるのがもっとも重要かもしれません。
Git ディレクトリの場所を指定する
ローカルリポジトリの初期化
Dropbox 内のディレクトリで --separate-git-dir
オプションに Dropbox 外のディレクトリを指定して git init
を実行します。
すでに Git 管理下にあるディレクトリで同じことをするとリポジトリの再初期化となり,Git ディレクトリの位置を変更することができます。
$ cd ~/Dropbox/project $ git init --separate-git-dir /path/to/project.git Initialized empty Git repository in /path/to/project.git/
指定した場所に Git ディレクトリが作成され,プロジェクトが Git の管理下に置かれました。
$ ls /path/to/project.git config description HEAD hooks/ info/ objects/ refs/
リポジトリとワークツリー
何もオプションをつけずに git init
を実行すると,カレントディレクトリに .git という名前で Git ディレクトリが作成されます。
--separate-git-dir
オプションで Git ディレクトリの場所を指定すると,カレントディレクトリには Git ディレクトリの絶対パスを記述した .git ファイルが代わりに作成されます。
$ ls -lA total 1 -rw-r--r-- 1 amano41 amano41 35 Nov 6 00:00 .git $ cat .git gitdir: /path/to/project.git
また,リポジトリの config に core.worktree
という項目が追加され,このリポジトリに対応するワークツリー(作業ディレクトリ)の絶対パスが保存されます。
$ git config --local core.worktree /home/amano41/Dropbox/project
普通に操作することが可能
リポジトリの作成が終われば,あとは普通の Git プロジェクトとして作業することができます。
$ echo "hello world" > hello.txt $ git add hello.txt $ git commit -m "First commit"
リモートリポジトリとのやり取りもまったく問題ありません。
$ git remote add origin git@github.com:amano41/project.git $ git push origin master $ git log --oneline --decorate --all 582972b (HEAD -> master, origin/master) First commit
別の端末でもコミットできるようにする
Dropbox でディレクトリを同期しながら,Git でバージョン管理ができるようになりました。 別の端末や移動中であっても,ネット環境があって Dropbox さえ使えれば最新の作業内容にアクセスできることになります。
しかし,別の端末から作業の続きをすることはできますが,このままではコミットの履歴を残すことができません。 Git で管理しているプロジェクトですからリモートリポジトリからクローンしてもよいのですが,そうすると Dropbox を使っている意味がなくなってしまいます。 別の端末でもディレクトリを Git の管理下に置き,Dropbox で同期した内容はそのままにして,リポジトリだけを最新の状態にする方法を考える必要があります。
状況の確認
別の端末でも Dropbox によってプロジェクトのディレクトリが同期されています。 この中にはリポジトリの実体(Git ディレクトリ)への参照である .git ファイルも含まれています。
$ cd ~/Dropbox/project $ ls -A .git hello.txt
しかし,この端末には .git ファイルに書かれている場所に Git ディレクトリがないため,git
コマンドを実行することはできません。
$ git status fatal: Not a git repository: /path/to/project.git
ローカルリポジトリの(再)初期化
まずはローカルリポジトリを作成して,このマシンでもプロジェクトを Git の管理下に置きます。
そのまま git init
を実行してもエラーとなるため,.git ファイルを削除 or リネームした上で git init
を実行します。
--separate-git-dir
オプションに先ほど指定したものと同じパスを指定することに注意してください。
.git ファイルの内容が変わってしまうと,先ほどの端末で Git がリポジトリを見つけられなくなります。
.git ファイルにはリポジトリの場所が絶対パスで書き込まれますが,作業ディレクトリからの相対パスに書き換えても動作するようです。
リポジトリの場所を端末間で一致させられない場合でも,作業ディレクトリからの相対的な位置関係を同じにしておけば対応できます。
--separate-git-dir
オプションで相対パスを指定しても .git ファイルには絶対パスで書き込まれるので,いちいち手動で編集しなくてはならないのが面倒ですが……。
$ rm .git $ git init --separate-git-dir /path/to/project.git Initialized empty Git repository in /path/to/project.git/
リポジトリが作成され,この端末でも Git によるバージョン管理ができるようになりました。 Dropbox のおかげでワークツリーは最新の状態になっていますが,リポジトリを作ったばかりなのでコミットの履歴はまだありませんし,どのファイルも追跡されていない状態です。
$ git log fatal: your current branch 'master' does not have any commits yet $ git status On branch master Initial commit Untracked files: (use "git add <file>..." to include in what will be committed) hello.txt nothing added to commit but untracked files present (use "git add" to track)
リモートリポジトリの情報にあわせる
これまでのコミット履歴を参照できるようにするため,git fetch
でリモートリポジトリから情報を取り込みます。
最初の端末で行った作業の記録が追跡ブランチ origin/master にコピーされます。
ワークツリーの内容は Dropbox によって同期されていますので,これで手元に前回の作業終了時の情報がすべて揃うことになります。
コミットがない状態 and/or ステージングされていない変更がある状態ではマージできませんので,git pull
ではエラーが出ます。
仮にマージできたとしても,Dropbox で同期したワークツリーの内容が上書きされてしまうので望ましくありません。
$ git remote add origin git@github.com:amano41/project.git $ git fetch origin master $ git log --oneline --decorate --all 582972b (origin/master) First commit
Dropbox で同期したワークツリーの内容を保持したまま HEAD とインデックスの状態を追跡ブランチ origin/master に一致させるために git reset
を実行します。
先ほど git pull
を使わなかったのと同じ理由で git checkout
は使い(え)ません。
リポジトリの情報が更新され,先ほどは未追跡の状態だったファイルもきちんと Git の管理するファイルとして認識されるようになります。
$ git reset origin/master $ git log --oneline --decorate --all 582972b (HEAD -> master, origin/master) First commit $ git status On branch master nothing to commit, working directory clean
最初の端末で作業を終えたときの状態が完全に再現されました。
リモートリポジトリからクローンしてくるのと違う点は,コミットしていなかった変更点も Dropbox のおかげで手元に反映されているということです。
コミットしていない変更点は git stash
で一時保存できますが,それをリポジトリ間でコピーすることはできません。
中断した作業を引き継ぐには,(一時的なブランチを切って)リモートリポジトリ経由でマージするか,Git を使わずに直接コピーするしかなかったのではないかと思います。
今回は後者のやり方を Dropbox に代わりにやってもらったことになります。
中断した作業を別の端末で再開する
Dropbox で作業ディレクトリを同期しながら,それぞれの端末で Git によるバージョン管理を行う環境が構築できました。 コミットの履歴はリモートリポジトリで共有していますので,いわば作業ログも同期していることになります。
以下では,ある端末で中断した作業の続きを,別の端末で再開する流れについて例示したいと思います。 とはいっても,やり方は基本的にローカルリポジトリの再初期化をしたときと同じです。 作業終了時にそれまでのコミットをプッシュしておき,作業する端末が変わったらフェッチして最新のコミットにリセットするだけです。
端末 A で作業する
いつも通りに作業を進めます。 今日は新しいファイルを作りました。
$ echo "good bye" > bye.txt $ git add bye.txt $ git commit -m "Add bye.txt"
その日の作業が終わったので,別の端末からもコミット履歴がたどれるようにリモートリポジトリにプッシュしておきます。 ある程度まとまった作業が終わってからプッシュするのが一般的だと思いますが,たとえば自宅に帰ってから続きをするつもりであれば今日コミットした分は必ずプッシュしておきます。
$ git push origin master $ git log --oneline --decorate 927bcd8 (HEAD -> master, origin/master) Add bye.txt 582972b First commit
端末 B で作業の続きをする
先ほど端末 A で作ったファイルが Dropbox によって同期されています。 この端末の Git にとっては作業ディレクトリに新しく追加されたファイルですので,これを追跡されていないファイルとして検知します。 もし,追跡対象となっている既存のファイルを変更していた場合には,ステージングされていない変更として検知されることになります。
$ ls bye.txt hello.txt $ git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) bye.txt nothing added to commit but untracked files present (use "git add" to track)
コミット履歴はリモートリポジトリがローカルリポジトリよりも先行している状態です。 リモートリポジトリが先行している分は端末 A でコミットしたものであり,現在のワークツリーはこのコミット(+α)の状態であることがわかっています。 コミットせずに中断していたり,出先や移動中にファイルを変更していたりすることもあるため,必ずしもワークツリー=最新のコミットではなく,+αの可能性があることに注意してください。
$ git fetch $ git log --oneline --decorate --all 927bcd8 (origin/master) Add bye.txt 582972b (HEAD -> master) First commit
ローカルリポジトリを再初期化したときと同じように,git reset
で HEAD とインデックスの状態を追跡ブランチ origin/master に強制的にあわせてやります。
$ git reset origin/master $ git log --oneline --decorate 927bcd8 (HEAD -> master, origin/master) Add bye.txt 582972b First commit $ git status On branch master nothing to commit, working directory clean
端末 A で作業を終えた状態(+α)が再現できましたので,あとはいつも通り作業の続きをします。 作業を終えるときには,それまでコミットした分をプッシュしておくのを忘れないでください。
端末での作業を終えるときの留意点
その日の作業を終えるときには,それまでにコミットした分をリモートリポジトリに反映させるのを忘れないようにします。 リモートリポジトリ経由でコミット履歴を取り込めない状態で Dropbox がワークツリーを同期すると,一度もコミットをしないまま多くの作業をしてしまったのと同じ状態になります。
プッシュするのを忘れていたとしても作業の続きをすることはできますが,別の端末での作業内容を部分的に修正することが難しくなりますし,あとでマージなりリベースなりする必要があって面倒です。