はじめに
後編の今回は、ブランチやマージ、リベースといったGitコマンドをAndroid Studioで実行するにはどうするか?といった内容で進めていきます。GitHubやリモートリポジトリの話はあまり登場しません。
ファイルの移動やリネーム
この章は機能の紹介ではなく、調査結果の報告です。Git連携中のファイル操作のうち移動とリネームがどのように行われるのか確認してみました。「 Projectツールウィンドウ」から2つのファイル(bar.txt
とfoo.txt
)をそれぞれ以下のように操作してみました。
bar.txt
を別のディレクトリに移動
foo.txt
をhoge.txt
にリネーム("Rename... "リファクタリングを実行)
「Versionツールウィンドウ」に記録された、それぞれの実行結果はリスト1 のとおりです。
リスト1 ファイルと移動とリネームの実行ログ
20:20:09.103: cd /Users/masanobuimai/AndroidStudioProjects/MyApplicationProject
20:20:09.104: git rm --ignore-unmatch -- bar.txt
rm 'bar.txt'
20:20:09.228: cd /Users/masanobuimai/AndroidStudioProjects/MyApplicationProject
20:20:09.229: git add --ignore-errors -- MyApplication/bar.txt
20:20:19.973: cd /Users/masanobuimai/AndroidStudioProjects/MyApplicationProject
20:20:19.974: git add --ignore-errors -- hoge.txt
20:20:20.064: cd /Users/masanobuimai/AndroidStudioProjects/MyApplicationProject
20:20:20.064: git rm --ignore-unmatch -- foo.txt
ログを見る限りではどちらもgit rm
とgit add
の組み合わせで移動とリネームを実行していました。「 git mv
を使ってないのに大丈夫だろうか?」と思いましたが、コミット後の「Changesツールウィンドウ / Logタブ」にてコミットログを確認すると、それぞれ「移動した(moved) 」と「リネームした(renamed) 」と記録されています。
図1 ファイルの移動とリネーム結果
念のためSourceTree で同じリポジトリのログを確認したところ、こちらでも「移動」と「リネーム」と認識されていました。さらにだめ押しで「Terminalツールウィンドウ」からgit mv
で、それぞれをファイルを移動/リネームして元に戻してみました。
図2 "git mv"でのファイルの移動とリネーム結果
結論としては、以下のとおりです。
Android Studioでは、移動・リネームには git mv
を使わず、git rm
と git add
を使う
ブランチ&マージ
Gitに限らず分散バージョン管理システムの真骨頂であるブランチ&マージについてです。筆者の私感ですが、Android Studioのブランチ&マージサポートは「まあまあイケてる」と思います。
ブランチの作成
辿り着く先のメニューは同じなのですが、辿り着く方法はいくつかあります。
"VCS Operations Popup... "から"Branches... "を実行する。
メニューバーの「VCS → Git → Branches...」を実行する。
ステータスバーのブランチ名をクリックする。
いずれも同じく図3 の「Git Branches」ポップアップが表示されます。
図3 「 Git Branches」ポップアップ
ここで「New Branch」を選択すると「今のブランチ」から枝分かれしたブランチを作成します。それ以外に「Local Branches」や「Remote Branches」の特定のブランチを選択してサブメニューの「Checkout as new branch」を実行すると「そのブランチ」から枝分かれしたブランチを作成を作成します。
特定のブランチの「最新の状態」ではなく「特定のリビジョン」からブランチを作成したい場合は「Changesツールウィンドウ / Logタブ」のコミットログからブランチを作りたいログを選び、コンテキストメニューから「New Branch」を実行します。
図4 「 Changesツールウィンドウ / Logタブ」のコンテキストメニューからブランチを作成する
ブランチの切り替え
先ほどの「Git Branches」ポップアップの「Local Branches」から任意のブランチを選択し、そのサブメニューから「Checkout」を実行します。当たり前ですが、すでに複数のローカルブランチが存在している状態でないと使えません。
これも一種の切り替えなのですが「特定のリビジョン」に切り替えたい場合は「Changesツールウィンドウ / Logタブ」のコミットログから特定の行を選び、コンテキストメニューの「Checkout Revision」を実行します。
このチェックアウトが完了すると、ステータスバーのブランチ名にはそのリビジョンのコミットハッシュ値が表示されます。
図5 特定のリビジョンに切り替えた場合の例
似たような方法で「Git Branches」ポップアップの「Checkout Tag or Revision」から切り替える方法があります。
図6 「 Checkout Tag or Revision」からのブランチ切り替え(クリックすると動きがわかります)
この「Checkout」ダイアログに任意のブランチ名やタグ名、コミットハッシュ値を入れて、切り替える事ができるのですが、コード補完が効かないなど正直やっつけ感がにじみ出てます(筆者は、まず使いませんね。今回の記事を書くために初めて使ったくらいです) 。
そういえば、どうゆうわけかブランチの切り替え(Checkout)は、"VCS Operations Popup... "やメニューバーの「VCS → Git」にはありません。ちょっと不思議ですね。
マージ
ブランチときたらマージです。マージも"VCS Operations Popup... "やメニューバーの「VCS → Git」から行う"Merge Changes... "と、おなじみの「Git Branches」ポップアップから行う方法の2通りありますが、ブランチの時と異なり辿り着く先が異なります 。
まずは"Merge Changes... "の場合、コマンドを実行すると図7 のようなダイアログが表示されます。
図7 「 Merge Branches」ダイアログ
ここでマージしたいブランチやそれに関連するオプションを指定して実行します。
もう一方の「Git Branches」ポップアップの場合、「 マージしたいブランチ」を選び、サブメニューから「Merge」を実行します。
図8 「 Git Branches」ポップアップからマージを実行する
こちらの場合、コマンド実行後に確認用のダイアログは表示されず即座にマージが実行されます。その際どのようなオプションが設定されたか「Version Controlツールウィンドウ / Consoleタブ」で確認してみましたが、直球勝負で「git merge <branch名>
」としているだけでした。
どちらの方法も現在のブランチ(ステータスバーに表示しているブランチ名)に対して、指定したブランチの内容をマージします。つまり常に次の関係になります。
「指定したブランチ」を 「 現在のブランチ」 に マージする
せっかくのGitなのでばんばんブランチを作ってマージしましょう!とは言っても慣れないうちはマージは怖いものです。すでにお気づきかと思いますが「Git Branches」ポップアップのサブメニューに「Compare」というのがあります。これを利用することで現在のブランチと指定したブランチを比較する事ができます。
図9 「 Git Branches」ポップアップからブランチ同士を比較する(クリックすると動きがわかります)
コンフリクトした場合
では何かしらの問題でマージでコンフリクトを起こしてみましょう。図10 のようにわざとファイルの更新を衝突させてマージでコンフリクトを起こします。
図10 マージ前のコミットログ
コンフリクトを検知すると図11 のようなダイアログが表示され、コンフリクト解決方法を促してきます。
図11 「 Files Merged with Conflicts」ダイアログ(クリックすると動きがわかります)
右側にあるボタンがコンフリクトの解決方法です。コンフリクトが起きたファイル一覧のうち、選択中のファイルに対してそれぞれ解決方法を指示していきます。
表1 「 Files Merged with Conflicts」ダイアログのボタンの意味
ボタン名 意味
Accept Yours 自分のファイルを採用する。
Accept Theris 相手(マージ側)のファイルを採用する。
Merge 手動でマージを行う。
「Files Merged with Conflicts」ダイアログで「Merge」を選択すると、さらに図12 のようなダイアログが表示され、手動でマージを行いコンフリクトを解決します。
図12 「 Merged Revisions for」ダイアログ(クリックすると動きがわかります)
「Merged Revisions for」ダイアログで「×」「 ≫」「 ≪」アイコンによる変更の取り込みを行い、すべての取り込みが完了すると図13 のようなダイアログが表示され、処理を完了するかどうかを聞いてきます。
図13 「 All Changes Processed」ダイアログ
ここで「Save and Finish」ボタンを押すと、コンフリクトは解決したとみなし自動的にコミットを行います。まだ修正したい箇所が残っている場合は「Continue」ボタンを押してコンフリクト解決を継続してください。
すべてのコンフリクトを解決すると自動的にコミットが行われます。
間違って、もしくは意図的にコンフリクトの解決前に「Files Merged with Conflicts」ダイアログを閉じた場合どうなるでしょう?コンフリクトが発生したステータスは残っているので、Android Studioがそれを自動検知してポップアップメッセージを表示します。
図14 コンフリクトの自動検知
または、メニューバーの「VCS → Git → Resolved Conflicts...」から再び「Files Merged with Conflicts」ダイアログを表示できます。コンフリクトの解決を中断しているとステータスバーに「Merging(マージ中) 」と表示されます。この状態はコンフリクトを解決してコミットするまで続きます。
余談ですが、コンフリクトの解決を一度中断して、あらためてそれを解決し、コミットしたときだけ任意のコミットログでマージすることができます。それ以外ではコミットは自動で行われるため、コミットログは一律で「Merge branch ''」となります(意図的というより、マージ処理の抜け道でたまたまそうできただけだと思います) 。
その他のGit操作
今まで説明してきた以外のGit操作について説明します。個人的な意見ですが、このあたりの細かな操作までAndroid Studioで実施する必要はないのでは?と思っています。「 一応できるけど、専用クライアントやコマンドラインから行った方が便利」という程度で眺めてください。
リセット(Reset)
リポジトリの状態を特定のリビジョンに戻す git reset
の方法です。メニューバーの「VCS → Git → Reset HEAD...」から行います。
たとえば図15 のような状況で「直前のコミット」を無かった事にしてみます。
図15 リセット前のコミットログ
"Reset HEAD... "を実行し、ダイアログに図16 のとおり入力して「Reset」ボタンを押します。
図16 「 Rest Head」ダイアログの入力例(クリックすると動きがわかります)
※「To Commit」には「HEAD~
」と入力しています。
リセットの方法(Reset Type)は「Mixed / Soft / Hard」の3種類から選び、戻りたいリビジョンを「To Commit」に指定して「Reset」ボタンを押します。この「戻りたいリビジョン」には「HEAD~
」や「HEAD~3
」と言った相対位置の指定かコミットハッシュ値を指定します(指定したリビジョンが正しいかどうかは「Validate」ボタンで確認することができます) 。
リセットが実行されると直前のコミットは取り消され、コミットログは図17 のようになります。
図17 リセット前のコミットログ
コミットハッシュ値の取得は「Changesツールウィンドウ / Logタブ」のコミットログから取得するのが最も簡単です。個人的には、この画面で指定したリビジョンにリセットできれば良いのにと思っていますが、なぜかチェックアウトしかできません。正直、便利だと思ったことはありません。
図18 「 Changesツールウィンドウ / Logタブ」からコミットハッシュ値を取得
リベース(Rebase)
続いてリベースです。こちらもメニューバーの「VCS → Git → Rebase...」から行います。リベースはタダでさえわかりづらい操作なので、いくつかの実例を元の使い方を紹介します。
ブランチ元の変更を取り込む
図19 のような状況で、masterブランチの変更をrebase-testブランチに取り込みます。
図19 リベース前のコミットログ
カレントがrebase-testブランチになっている状態で"Rebase... "を実行します。masterブランチの変更を取り込むので「Onto」に「refs/heads/master」を指定します。
図20 「 Rebase branch」ダイアログの入力例
リベースを実行すると「Rebasing Commits」ダイアログが表示されるので、取り込むコミットログすべての「Action」を「pick」に指定して「Start Rebasing」ボタンを押します(なお「Action」は「pick / edit / skip / squash」から選択できます) 。
図21 「 Rebaing Commits」ダイアログ(クリックすると動きがわかります)
結果、masterブランチの変更は取り込まれコミットログは図22 のようになります。
図22 リベース後のコミットログ
コミットをまとめる
次に複数のコミットをまとめる例です。図23 のようなコミットログから直前の2つのコミットログを3つ前にまとめてみます。
図23 リベース前のコミットログ
"Rebase... "を実行して、「 Rebase branch」ダイアログの「Onto」に「HEAD~3」と入力し「Rebase」ボタンを押します(「 Onto」以外はデフォルトのままで良いです) 。
また「Rebasing Commits」ダイアログが表示されるので、下2つのコミットに対して「Action」に「squash」を指定します。
図24 「 Rebaing Commits」ダイアログの指定例(クリックすると動きがわかります)
※先頭のコミットを「squash」に指定すると「The first non-skip commit could not be marked as squashed since squash merges commit with the previous commit」と警告が表示されリベースを開始できません(コミットを寄せる先がなくなるので、そういうものなのでしょう) 。
「Start Rebaing」ボタンを押して、しばらくすると図25 のような「Additional Rebase Input」ダイアログが表示されます。
図25 「 Additional Rebase Input」ダイアログの例
特にメッセージを変更する必要がなければ、そのまま「Resume Rebasing」ボタンを押してリベースを完了します。結果、コミットはまとめられて図26 のようになります。
図26 リベース後のコミットログ
リベース悲喜こもごも
どういうわけかステータスバーは「Rebasing(リベース中) 」のままなのに、メニューバーの「VCS → Git」にあるリベース関係のメニューが使えなくなる場合があります。こうなるとAndroid Studio上ではどうしようもないので「Terminalツールウィンドウ」から「git rebase --continue
」や「git rebase --abort
」などを実行して回復を行ってください。
図27 「 Terminalツールウィンドウ」からリベースを中断(クリックすると動きがわかります)
※できたてホヤホヤのためか「Terminalツールウィンドウ」はAndroid Studioのバージョンによって動かない時があります。
傾向としては、コミットの修正(「 Rebasing Commits」ダイアログで「edit」を指定)は途中でわけがわからなく確率が高いです。正直、この手の作業はAndroid StudioではなくSourceTreeなどの専用クライアントかgit
コマンドから行うほうが無難だと思います。
図28 Android Studioではイマイチ不調な「edit」アクション
一見複雑そうな「Rebase branch」ダイアログですが、言うなればgit rebase コマンドのフロントエンドに過ぎません。git rebase
のSYNOPSIS(リスト2 )とダイアログの設定項目の対比は表2 の通りです。
リスト2 git rebaseのSYNOPSIS
git rebase [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
表2 「 Rebase branch」ダイアログの項目と「git rebase」コマンドの対比
ダイアログの設定値
git rebase
の該当箇所備考
Git Root 該当なし Android Studioは複数のGit Rootを持てるので、それを選択する(通常は1つ)
Branch [<branch>]
通常はカレントのブランチ
Interactive `[-i | --interactive]`
Preserve Merges `[-p | --preserve-merges]`
Onto [--onto <newbase>]
<newbase>
を指定する
From [<upstream>]
Ontoを指定していること(省略するとOnto全体が対象)
Merge Strategy [-s <strategy>]
「 / resolve / recursive / octopus / ours / subtree」から選択する
Do not use merge strategies `[-m | --merge]` チェックを外すと -m
オプションを付けない。
タグ打ち(Tag)
一番簡単な方法は「Changesツールウィンドウ / Logタブ」のコンテキストメニューから「New Tag」を実行することです。
図29 「 Changesツールウィンドウ / Logタブ」からタグを打つ(クリックすると動きがわかります)
もうひとつの方法はメニューバーの「VCS → Git → Tag Files」を実行することです。図30 の「Tag」ダイアログでもわかるように「Commit」欄にコミットハッシュ値や「HEAD~
」のような位置指定する必要があります。正直便利とは言えません。
図30 「 Tag」ダイアログ
タグを打つには申し分ない程度の機能は提供しているのですが「Changesツールウィンドウ / Logタブ」に必ずすべてのタグやブランチが表示されるわけではないようです。そのため、運がよければタグを確認できますが、多くの場合はタグの設定に気付くのは難しいと思います。
図31 コミットログ上からタグを確認できない
※選択しているコミットログにはタグが打たれています。
タグがまったく表示されないわけではありません。現に「HEADタグ」は表示されており、他のタグもなんらかの拍子で表示されるようです。そういえばブランチも常に全てが表示されませんので、もしかしたら、バグなのかも知れません。
SourceTreeなど他のクライアントからログを参照すれば、ちゃんとタグが打たれていることを確認できます。
図32 SourceTree上ではタグが確認できる
コミットの抜き取り(Cherry Pick)
特定のコミットポイントの変更を抜き取る git cherry pick
です。これは「Changesツールウィンドウ / Logタブ」からのみ操作できます。「 なぜ、これだけ?」と思うように、結構ムリヤリっぽい操作になっています。
これも実例付きで説明します。図33 のような状態で「master/HEAD」に「branch2」の最新ひとつ前の変更を抜き取ってみます。
図33 抜き取り前のコミットログ
「Changesツールウィンドウ / Logタブ」から抜き取りたいコミットログを選択し、コンテキストメニューかツールバーから「Cherry-Pick 」を実行します。
図34 「 Cherry-Pick」の実行(クリックすると動きがわかります)
抜き取った変更箇所は、いつも使っている「Deafult」ではなく、専用の「Changelist」に格納されます。「 Changelist」の説明もまだ行っていないのでわかりづらいことこの上ないかと思います。筆者自身も「これなら専用クライアントで作業したほうがもっと簡単かな」と思っているほどです。
変更の一時待避と復帰(Stash/Unstash)
メニューバーの「VCS → Git」にある "Stash Changes... " が一時待避で、もうひとつの "Unstash Changes... "でその復帰を行います。
図35 「 Stash」ダイアログ
「Android Studioでは、そんなものだ」と言われればそうなのですが、普段どれだけStashされているか確認する方法はありません。"Unstash Changes... "を実行してはじめて、このリポジトリが保有しているStashの一覧を確認することができます。
図36 「 Unstash Changes」ダイアログ
SourceTreeではStashの一覧を常に参照できるので、この手の操作は無理にAndroid Studioでやらないほうが良いのでは?と思います。
図37 SourceTreeのサイドバー
また、きちんと説明していませんが、Android Studioには独自の一時待避機能(Shelve)というのがあります。インターフェイスのわかりやすさや使い勝手では、だんぜんShelveのほうが優れています。要するにStash/Unstashサポートがイケてないのです。
まとめ
Android StudioのGitサポートについて一通り紹介しました。イマイチなところもありますが、普段使いする分には実用に耐えられる出来なのではと思います。
おさらいになりますが、次の操作はAndroid Studioではできませんので、専用クライアントかコマンドラインで処理してください。
リモートリポジトリの追加(git remote add
)
コミットの取り消し(git revert
)
タグの削除(git tag -d
)