【Unity・GitHub Desktop】過去の状態から作り直したゲームをmainに反映する方法|RevertとCherry-pickを図解

広告

Unityでゲームを制作していると、こんな場面があるかもしれません。

「昨日までは正常に動いていたのに、いろいろ変更したらゲームが動かなくなった」

「以前の正常だった状態から作り直したい」

「作り直したゲームを、チームで使用しているmainブランチに反映したい」

Gitには、過去の状態に戻る機能があります。

しかし、チーム開発では単純にmainを過去へ戻すと、ほかのメンバーの作業に影響することがあります。

そこで今回は、GitHub DesktopのRevertとCherry-pickを組み合わせ、不要な変更を取り消して、作り直したUnityプロジェクトをmainに反映する方法を紹介します。

Gitコマンドを入力せず、GitHub Desktopの画面操作を中心に進めます。

なお、この記事で紹介するのは、コミットが直線的に並んだ比較的単純な例です。実際のチーム制作では、変更内容によって競合が発生することがあります。その場合の考え方も後半で説明します。

1. 今回やりたいこと

まず、次のような開発履歴を考えてみましょう。

mainは、ゲームをデプロイ(配布・公開)するためのブランチです。

それぞれのコミットには、次のような変更が記録されています。

コミット作業内容
AUnityプロジェクトを作成
Bプレイヤーの移動処理が完成
C敵の処理を追加したが問題が発生
D修正を試みたが、さらに問題が発生
EBから作り直して敵の処理を実装
F作り直したゲームが完成

現在のmainにはA・B・C・Dの履歴があります。

一方、Bから分岐したretry-gameには、A・B・E・Fの履歴があります。

今回は、CとDの変更を取り消し、EとFの変更をmainへ取り込みたいのです。

ここで注意したいのは、単純にretry-gameをmainへマージするだけでは、CとDの変更が自動的に削除されるわけではないことです。

通常のマージでは、両方のブランチの変更を統合しようとします。

そのため、CとDを不要と判断した場合は、それらの変更を明示的に取り消す必要があります。

2. RevertとCherry-pickを理解しよう

今回使用するGitの機能は2つです。

Revert:過去の変更を打ち消す

Revertは、指定したコミットの変更を打ち消すための新しいコミットを作る操作です。

例えば、次の図のように、mainにA・B・C・Dの履歴があり、CとDをRevertしたとします。

R(D)・R(C)は、DとCの変更を打ち消すコミットを表しています。

ここが重要です。

Revertしても、C・Dというコミット自体は削除されません。

過去の履歴を残したまま、その変更を取り消すのです。

チーム開発では、この性質が大きな利点になります。ほかのメンバーの手元にある履歴と食い違わないため、安全に取り消せるのです。

Cherry-pick:別のブランチから変更を取り込む

Cherry-pickは、あるブランチの特定のコミットを選び、その変更を別のブランチにコピーする操作です。

例えば、次の図のように、retry-gameのEとFの変更を、restore/f-version(後の手順で作る統合用ブランチ)に取り込むとします。

Cherry-pickを使うと、EやFで行った変更を、別のブランチに取り込めます。

取り込み先では、同じ変更を表す新しいコミットが作られます。元のE・Fそのものが移動するわけではありません。この記事では、コピーされたコミットをE'、F'と書き表します。

4つの操作の違い

RevertとCherry-pickの違いを、MergeやResetと並べて整理すると、次のようになります。

操作役割
Revert指定したコミットの変更を打ち消す(新しいコミットが増える)
Cherry-pick指定したコミットの変更を別ブランチにコピーする
Merge2つのブランチの開発履歴を統合する
Resetブランチの参照先を別のコミットに移す(履歴が巻き戻る)

mainをResetで巻き戻す方法もありますが、すでにチームで共有している履歴を書き換えることになります。ほかのメンバーの履歴と食い違うおそれがあるため、今回は履歴を残したまま進められるRevertとCherry-pickを使います。

今回は、RevertでC・Dを打ち消し、Cherry-pickでE・Fを取り込みます。

3. なぜこの2つの組み合わせで目的の状態になるのか

操作の手順だけを覚えても、「なぜこれでFと同じ状態になるのか」が分からないと、いざという時に応用できません。ファイルの内容に注目して考えてみましょう。

  1. DをRevertすると、Dで加えた変更が取り消され、Cの内容に戻ります。
  2. CをRevertすると、Cで加えた変更も取り消され、Bの内容に戻ります。
  3. Eは「Bから見た変更」として記録されています。Bと同じ内容の上にEの変更を適用すると、Eの内容になります。
  4. FはEから見た変更です。Eの内容の上にFの変更を適用すると、Fの内容になります。

つまり、Revertで一度Bの内容まで戻してからE・Fを順に重ねるので、最終的にFと同じ内容になるのです。

この考え方が成り立つ条件は、次のとおりです。

  • mainがA・B・C・Dのままで、C・D以外の変更が混ざっていない
  • E・Fが、Bを出発点として作られている
  • 途中で競合が発生した場合も、解決後の内容がFと一致している

実際のプロジェクトで条件が崩れると、競合が起きたり、結果がFと少し違ったりします。そのため、この記事では最後に「Fと同じ内容になったかを確認する手順」も紹介します。

4. 作業前の準備

GitHub Desktopを開く前に、次の点を確認しましょう。

  • Unityのシーンを保存していること
  • 必要な変更がコミットされていること
  • retry-gameのFまでの開発が完成していること
  • retry-gameがGitHubにプッシュされていること(後の比較で使います)
  • mainの最新状態を取得していること
  • チームのメンバーと作業方針を共有していること

Unity Editorは、ブランチの切り替え前に終了しておくと安全です。

GitHub Desktopでブランチを切り替えると、シーンやスクリプトなどのファイルも切り替わります。

Unityを開いたまま操作すると、再インポートやファイルの再読み込みが発生する場合があります。

また、今回はmainを直接変更するのではなく、統合専用の新しいブランチを用意します。

全体の流れ

ここまでの内容と、これから行う操作を1枚にまとめました。クリックすると拡大して表示されます。

5. mainから統合用ブランチを作成する

GitHub Desktopを起動し、対象のUnityリポジトリを開きます。

まず、Fetch originでリモートの更新を確認し、必要に応じてmainをPullします。

その後、次の操作を行います。

  1. 上部のCurrent Branchをクリック
  2. mainを選択
  3. BranchメニューからNew Branchを選択
  4. ブランチ名にrestore/f-versionと入力
  5. 作成元がmainであることを確認
  6. Create Branchをクリック

これで、mainの現在の状態を引き継いだ新しいブランチが作成されます。

この時点では、統合用ブランチのUnityプロジェクトはDの状態です。

6. Dの変更をRevertで取り消す

次に、Dの変更を取り消します。

GitHub Desktopでrestore/f-versionを選択していることを確認してください。

  1. 左側のHistoryタブをクリック
  2. Dのコミットを右クリック
  3. Revert Changes in Commitを選択

これでDの変更を打ち消すコミットが新しく作成されます。

履歴は次のようになります。

Dは履歴に残っていますが、Dで行った変更は打ち消されています。

7. Cの変更もRevertで取り消す

続いて、Cについても同じ操作をします。

  1. HistoryからCのコミットを探す
  2. Cを右クリック
  3. Revert Changes in Commitを選択

これで、Cの変更も打ち消されます。

この例では、最終的なファイル内容はBの状態に戻ります。

なぜDからCの順番なの?

複数のコミットをRevertするときは、新しいコミットから古いコミットの順に取り消すことが推奨されています。順番を逆にすると、マージコンフリクトが発生する場合があるためです。

今回はDがCより新しいため、D → Cの順番で操作します。

8. retry-gameからEをCherry-pickする

ここからが、新しく作り直したゲームの変更を取り込む操作です。

ここで間違えやすいポイントがあります。 Historyタブには、現在選択しているブランチの履歴が表示されます。restore/f-versionを選んだままではEは見つかりません。取り込む元であるretry-gameに切り替えてから操作します。

  1. Current Branchをクリックし、retry-gameに切り替える
  2. Historyタブを開く
  3. Eのコミットをクリックして選択
  4. Eを右クリックし、Cherry-pick commit…を選択
  5. コピー先のブランチ一覧からrestore/f-versionを選ぶ

これで、Eの変更が統合用ブランチに取り込まれます。

右クリックのかわりに、HistoryのEを画面上部のCurrent Branchまでドラッグし、ブランチ一覧が開いたらrestore/f-versionの上にドロップする方法もあります。

Cherry-pickが完了すると、現在のブランチは取り込み先のrestore/f-versionに自動で切り替わります。

そのため、次にFを取り込む前に、もう一度retry-gameへ切り替える必要があります。

9. 続いてFをCherry-pickする

次はFのコミットです。

  1. Current Branchでretry-gameへ戻る
  2. HistoryからFを探して選択
  3. Fを右クリックし、Cherry-pick commit…を選択
  4. コピー先のブランチとしてrestore/f-versionを選ぶ

これでEに続いてFの変更も取り込まれます。

E → Fの順番で取り込むことが大切です。

FがEで追加したクラスやPrefabを利用している場合、Fだけを先に取り込むと必要なファイルが不足したり、競合が発生したりする可能性があります。

GitHub Desktopでは、複数のコミットを選択してまとめてCherry-pickすることもできます。MacではCommandキーやShiftキー、WindowsではCtrlキーやShiftキーを使って複数選択します。

ただし、初学者にはEとFを1つずつ取り込む方が、何が起こっているかを理解しやすいでしょう。

取り込みが終わったら、restore/f-versionのHistoryを開いてみましょう。上から次の順に並んでいれば、ここまでの操作は計画どおりです。

10. UnityでFと同じ状態になったか確認する

ここまでの操作で、統合用ブランチには次の変更が加えられました。

  • Dの変更を打ち消した
  • Cの変更を打ち消した
  • Eの変更を取り込んだ
  • Fの変更を取り込んだ

図にすると、次のようになります。

E'とF'は、Cherry-pickによって作られたコピーのコミットです。

ここで注意してください。

操作が成功しただけで、必ずFと完全に同じファイル内容になったとは限りません。

特にコンフリクトを解決した場合は、意図しない変更が残っていないか確認する必要があります。

Unityで確認する項目

restore/f-versionに切り替え、Unity Hubからプロジェクトを開きます。

次の項目をチェックしましょう。

  • Consoleにコンパイルエラーが出ていないか
  • 正しいシーンが開けるか
  • プレイヤーの操作が正常に動くか
  • 敵の出現や攻撃処理が正常に動くか
  • Prefabの参照が切れていないか
  • InspectorでMissingになっている参照がないか
  • 必要な素材やアセットがそろっているか

今回の目的は、C・Dの問題を取り除き、Fで完成したゲームを反映することです。

単にエラーが出ないだけでなく、Fと同じ動作ができることを確認します。

11. GitHubへ送って、Fとファイル内容を比較する

動作確認が終わったら、統合用ブランチをGitHubへ送ります。

  1. GitHub Desktopでrestore/f-versionを選択
  2. Publish branchまたはPush originをクリック

プッシュが終わったら、Unityでの動作確認に加えて、ファイル内容そのものがFと同じかも比較しておきましょう。

2つのブランチの最新状態を直接比較する

ブラウザで、次の形式のURLを開きます。ドット(.)が2つである点に注意してください。

https://github.com/ユーザー名/リポジトリ名/compare/retry-game..restore/f-version

ドット2つの比較(two-dot)は、2つのブランチの最新の状態同士を直接比べる形式です。ファイルの差分が表示されなければ、Gitが管理しているファイルの内容はretry-gameのFと一致しています。差分が表示された場合は、そのファイルの内容を確認し、意図した違いかどうかを判断します。

Pull Requestの画面では確認できない理由

GitHubのPull Requestは、ドット3つの比較(three-dot)で差分を表示します。これは「2つのブランチが分かれた地点(今回ならB)から、何が変わったか」を示す形式です。

そのため、今回のように履歴の形が違う2つのブランチを比べても、E・Fや打ち消しコミットの変更が表示されます。「最新のファイル内容が一致しているか」の確認には向きません。

ターミナルを使う場合

ターミナルを使える環境なら、次のコマンドでも確認できます。

git diff --exit-code retry-game restore/f-version

差分がなく終了コードが0なら、Gitが管理しているファイル内容は一致しています。

ただし、このコマンドは追加の確認方法です。RevertとCherry-pickの基本操作自体にはターミナルは必要ありません。

12. Pull Requestを作成してmainに反映する

内容を確認できたら、Pull Requestを作成します。

  1. GitHub DesktopのCreate Pull Requestをクリック
  2. ブラウザでPull Request作成画面が開く
  3. マージ先のbaseがmainであることを確認
  4. 比較元のcompareがrestore/f-versionであることを確認
  5. 変更内容をレビューしてPull Requestを作成

Pull RequestのFiles changedには、mainの現在の状態(D)から、F相当の内容に変わる差分が表示されます。レビューする人は、この差分を見れば「今回のマージでmainの何が変わるか」を把握できます。

チームメンバーにレビューを依頼し、問題がなければmainへマージします。

マージ後は、GitHub Desktopでmainに切り替えてPull originを行い、手元のmainを最新にしておきましょう。

これで、C・Dの不要な変更を取り除き、E・Fの変更を反映したUnityプロジェクトをデプロイ対象にできます。

ただし、Pull Request作成中にほかのメンバーがmainを更新した場合は、マージ後の内容がFと完全に一致するとは限りません。

マージ直前にも、main側の更新状況を確認しましょう。

13. CとDのコミットは消えてしまうの?

ここは、Gitを理解するうえで重要なポイントです。

CとDの履歴は消えません。

Revertによって、変更を取り消す新しいコミットが追加されるだけです。

したがって、開発チームでは次のようなことが可能です。

  • なぜCとDの実装が失敗したのか、後から確認する
  • CとDで行った変更を調査する
  • 必要があれば過去の実装を参考にする
  • 誰がいつ修正したかを追跡する

「消えた」のではなく、「打ち消すコミットが追加された」と覚えておくと、履歴を見たときに混乱しません。

Gitは、単にファイルを保存するだけの仕組みではありません。

開発の過程を記録し、必要な変更を選択して管理する仕組みなのです。

14. Unityプロジェクトで注意したいファイル

Unityのプロジェクトでは、C#のスクリプトだけがGitの管理対象ではありません。

通常、次のようなファイルもGitで管理します。

ファイル・フォルダー内容
Assetsスクリプト、シーン、Prefab、画像など
.metaアセットの識別情報など
Packagesパッケージ依存関係など
ProjectSettingsUnityプロジェクトの設定

一方、LibraryやTempなどは通常Git管理対象外です。

特に注意したいのが、.metaファイルです。

Unityでは、アセットの参照にGUIDが利用されています。

そのため、Prefabやシーンを変更するとき、.metaファイルとの整合性が崩れると参照切れが発生する場合があります。

また、シーンやPrefabは通常、YAML形式で保存されます。

Gitで変更内容を比較できますが、複雑なシーンでは競合が発生する可能性があります。

競合が発生した場合は、GitHub Desktopの解決結果だけで判断せず、UnityでシーンやPrefabを実際に開いて確認してください。

なお、Gitで管理していないファイルはRevertやCherry-pickでは復元されません。Git LFSを使用している場合も、大容量アセットが正常に取得できているか確認しましょう。

15. Revert + Cherry-pickが向いている場面

今回の方法が向いているのは、次のようなケースです。

  • mainには不要なコミットが残っている
  • 正常だった過去の状態から別ブランチで作り直した
  • 不要な変更の範囲が明確になっている
  • 元の履歴は削除したくない
  • Pull Requestによるレビューを行いたい

一方、C・Dの変更が多数のコミットに分散していたり、ほかの開発者による変更と複雑に絡み合っていたりする場合は、RevertとCherry-pickだけでは整理が難しくなることがあります。

その場合は、mainの最新状態から新しい統合用ブランチを作り、必要なファイルを目的の状態に揃えてコミットする方法も検討できます。

大切なのは、操作方法を機械的に覚えることではなく、最終的にどの変更を残し、どの変更を取り消したいのかを明確にすることです。

16. もっと簡単な方法はないの?

ここまで読んで、「手順が多い」と感じた方もいるかもしれません。取り消したいコミットが増えるほど、Revertの操作も増えていくためです。

目的が「mainの内容を、作り直したFと同じにすること」だけなら、次のような方法もあります。ただし、それぞれ得意なことが違います。

ターミナルを使える場合:1つのマージでFの内容にする

履歴を残したまま、mainの内容をFと同じにする方法です。retry-gameに、mainを「内容は取り込まない」マージで取り込みます。

git switch retry-game
git fetch origin
git merge -s ours origin/main
git push origin retry-game

-s oursは、マージ結果の内容を常に現在のブランチ(ここではretry-game)のままにして、相手のブランチの変更を無視するマージ方法です。mainの履歴(A〜D)はretry-gameの履歴に取り込まれますが、C・Dの変更は内容に反映されません。

あとは、baseをmain、compareをretry-gameにしてPull Requestを作成し、mainにマージします。その間にmainが更新されていなければ、マージ後のmainはFと同じ内容になります。

なお、-s oursと-X oursは別物です。-X oursは競合した部分だけを自分側にする指定で、競合しなかった変更は残ります。

この方法では、C・Dを打ち消すコミットは作られません。履歴で「取り消した」ことを明示したい場合は、この記事のRevertを使った方法のほうが分かりやすくなります。

GitHub Desktopだけで行う場合:mainをretry-gameにマージする

  1. マージの前に、Fのコミットを右クリックしてCopy SHAで控えておく
  2. retry-gameに切り替える
  3. BranchメニューのMerge into current branch…でmainを選ぶ
  4. 競合が出たら、F側(retry-game側)の内容になるように解決する
  5. retry-gameをプッシュし、第11章と同じ形式のURLで、控えておいたFのコミットとretry-gameを比較する(compare/Fのコミット..retry-game)
  6. 差分が出たら、その分を取り除いてコミットする
  7. Pull Requestを作成して、mainにマージする

手順は少なく見えますが、競合の解決と、差分の取り除きを自分で行う必要があります。C・Dで追加したファイルのうち、Fに存在しないものは、競合せずにretry-gameへ入ってくるためです。

どの方法を選ぶか

  • Fと同じ内容になることを確実にしたい、ターミナルも使える:ターミナルの方法(-s ours)
  • ターミナルを使わず、履歴で「C・Dを取り消した」ことを残したい:この記事のRevert + Cherry-pickの方法
  • 手順を減らしたいが、確認と修正の手間はかけられる:GitHub Desktopだけでmainをマージする方法

どの方法でも、最後にUnityで動作を確認し、Fとファイル内容を比較する手順は変わりません。

17. まとめ

今回は、Unityプロジェクトを過去の状態から作り直し、その内容をmainに反映する方法を紹介しました。

流れを整理すると、次のようになります。

  1. mainから統合用ブランチを作成する
  2. 不要なコミットDをRevertする
  3. 続いてCをRevertする
  4. retry-gameに切り替えて、EをCherry-pickする
  5. もう一度retry-gameに切り替えて、FをCherry-pickする
  6. Unityで動作を確認する
  7. GitHubへプッシュし、two-dotの比較でファイル内容を確認する
  8. Pull Requestを作成し、mainにマージする

この方法なら、mainの過去の履歴を残したまま、不要な変更を取り消し、作り直したゲームを反映できます。

そして、今回覚えてほしいのは次の2つです。

Revertは「過去の変更を打ち消す操作」。

Cherry-pickは「必要なコミットの変更を別のブランチへコピーする操作」。

この違いを理解しておけば、Unityのチーム制作で問題が発生したときも、より落ち着いてGitの履歴を扱えるようになります。

参考資料

訪問数 10 回, 今日の訪問数 10回

広告