こんにちは。
ソーイ株式会社、入社2年目の村上です。
以前、GitHub Desktopで使用される基本的なGitコマンドについて紹介しました。
前回の記事はこちらです。
前回は、
- clone
- commit
- push
- pull
- branch
など、基本的なGit操作を中心に紹介しました。
今回はその続きとして、 GitHub Desktopには存在する少し概念が難しい操作について紹介します。
今回紹介するものの中には、少しタイミングや操作を誤ってしまうと重大な問題になってしまう要素もあります。
こういったものがGitHub Desktopでは簡単なものと同列の場所に配置されているのでなかなかに怖いです。
ですが使い方さえ間違えなければ大変便利でもあります。
なんかあるけど使い方わからんな
という思いで使わないのももったいないです。
自分の備忘録的な側面もありますが、この記事が機能の怖さと便利さに向き合うきっかけの一歩として使われると幸いです。
この記事のターゲット層
この記事は、以下のような方に届けば幸いです。
GitHub Desktopを使用している方
GitHub Desktop内部でどのようなGitコマンドが動いているのか知りたい方
Git の少し応用的な機能を理解したい方
この記事で分かること
本記事では、GitHub Desktopの少し応用的な操作が、
内部でどのようなGitコマンドとして実行されているのかを対照形式で解説します。
- コミット修正と履歴変更(Amend / Undo Commit)
- 特定コミットのみの適用(Cherry-pick)
- branch間の差分確認(Compare to Branch)
- コミット履歴整理の方法(Squash Commits)
- 履歴変更系操作を扱う際の注意点(force push など)
GitHub Desktop 操作 vs Gitコマンド 対応一覧
| GitHub Desktop操作 | Gitコマンド | 動作の役割 |
|---|---|---|
| Cherry-pick Commit | git cherry-pick |
特定コミットだけ適用 |
| Amend | git commit --amend |
最後のコミットを修正 |
| Undo Commit | git reset --soft |
コミットのみ取り消し |
| Compare to Branch | git diff |
branch間差分確認 |
| Squash Commits | git rebase -i |
複数コミットを統合 |
UIと動作するコマンド解説
1. Cherry-pick
あるブランチの特定コミットだけを別ブランチへ持っていく操作のことです。
UI操作
GitHub Desktop上ではHistoryから該当のコミットを右クリック。
すると画像のようなメニューの中にCherry-pick Commit...という枠があるのでそちらをクリック。
するとどのブランチに送るか選択することができます。
この他にも、該当コミットをツール画面上のCurrent Branchにドラッグすれば同様の操作を行ったことになります。
コマンド
コマンドでCherry-pickする場合はコミットをコピーしたいブランチ上で以下コマンドを入力します。
git cherry-pick [commit-id]
入力することで、指定したコミットの変更内容を、現在のブランチへ適用できます。
概要
例えば以下のような履歴があるとします。
feature
├ 修正A
├ 修正B
└ 修正C
hotfix
└ 修正D
このうち「うわー修正Bだけ先にhotfixのブランチに適用したいなー」という状況で、このCherry-pickを使用します。
使うと、以下のような履歴に変化します。
feature
├ 修正A
├ 修正B
└ 修正C
hotfix
├ 修正D
└ 修正B
別ブランチに変更コミットごとコピーするイメージですね。
問題として適用する差分の履歴をコピーしているわけですからコンフリクト問題があります。
その場合はMergeした時と同様に、競合箇所を手動で解決する必要があります。これが少し面倒なので、取り込む際には気を付ける必要があります。
2. Amend
直前のコミットの内容を修正する操作です。
UI操作
コミット後に差分を追加したい場合やコミットのメッセージを変更したい場合に使います。
コミット後にHistoryを確認すると、Amend Commit... という選択肢が一番上にあるので、こちらをクリックすると、
Changesでファイルの追加とコミットのタイトル、コメントの修正を行えます。
この時だけcommitボタンがAmend last commitに変化します。
この状態で差分を追加したり、タイトルを変更したりすると、履歴に反映されるわけですね。
注意点として、Pushした後のCommitに向かってAmendしようとすると、以下の表記が出ます。
この画像で示されているのは
すでにPush済みのブランチをAmendしようとしてるけどこれForce Push必要だよ?大丈夫?
みたいなニュアンスのことを言ってます。
こちらの内容に関しては後ほどお話しします。
コマンド
Amendをコマンドで行う際は作業を行っているブランチ内で
git commit --amend
上記を入力すると、直前のコミットを作り直すことができます。
概要
さてAmendですが、行っていることは最後のCommitを作り直す行為です。
つまり
コミットA
↓
Amend
↓
コミットA(編集済みA)
のような状態になるわけです。
見た目としては最後にしたコミットの内容を修正して再度送り出したという処理に見えますが、実際は別のコミットとして作り直すということを行っています。
ここで1つ説明するのですが、GitのCommitにはそれぞれ一意のIDが割り振られています。
GitHub Desktopでいうと、下記の場所に記述がありコピーが可能です。
このIDによってGitは履歴を管理しているわけですね。
で今回のAmendは直前のものを作り直すということで、このIDも変化してしまうわけです。
Push後のコミットをAmendして編集すると、以下の状態になります。
ローカル
A(編集済みA)
リモート
A
こうなると、リモートの履歴と一致しない -> Push不可能という話になります。
ここで使われるのが、Force Pushです。
これを使うと 履歴を強制的に更新しリモートに反映させることができてしまいます。
この行為になぜ警告を示す表示がされているかというと、仮に
A
↓
B
↓
C
という履歴がすでにリモートにあったとして、田中さんがその履歴を取得していたとします。
仮にチームの誰かがCに対しAmendを行ったとします。
同時期に田中さんがCからDというコミットを作成したとします。
すると、Gitは「田中さんがDというコミットを作っているけど、これは何のコミットから派生したもの???」
となり、もう存在しないコミットを親として作ったコミットが履歴に乗る -> 不整合が発生するという状況が生まれます。
こうなると追加対応が必要になってしまうので、チーム内の複数人で作業を行う場合はPush前にAmendを使うようにすることをお勧めします。
3. Undo Commit
Gitで作業中たまにあることなのですが、
「うわ~ この修正入れ忘れた~」
「なんかこのコミット分けなおしたいな~」
という時があります。
Undoの機能としては直前のコミットを取り消す操作になります。
UI操作
Historyタブで直前のコミットを確認
リストからUndo Commitを選択する
これだけでコミットが取り消され、差分やタイトルなどがコミット前の状態に戻されます。
コマンド
コマンドで使う場合は
git reset --soft HEAD~1
で、コマンド限定で
git reset --hard HEAD~1
というのもあります。
概要
Amendとは違い、コミットを取り消すという認識でOKです。
説明しやすいのでコマンド軸で説明します。
softのほうは、挙動としてGitHub Desktopのものに近く、コミット待ち状態まで戻すというのが内容です。
こっちは差分追加したいとかコミットメッセージ間違えたとかの修正として使えます。
A
↓
B
↓
C
↓
A
↓
B
でもCの内容は残す。
コミットだけなかったことにするイメージ。
対してhardのほうは、Undoしたコミットで修正した差分もすべて消えます。
試しに入れてみたけどいらんな~というような内容を削除するときにおすすめです。
A
↓
B
↓
C
↓
A
↓
B
Cの内容は残らない。
コミットも変更内容もなかったことにするイメージ。
この--hardは変更した差分ごと消えるので安易に使うとろくなことがないです。一旦確認の意味を込めて--softを使うことをお勧めします。
GUIツールとして、優しめの--softを採用する親切設計になっているといえますね。
4. Compare to Branch
開発を進めていると
別のブランチとの差分を比較したいな~
なんてことがあったりなかったり。
自分はとりあえずGitHubでPRを作れば差分として一覧に出てくれるのであまり使わないですね。
UI操作
画面上のリストから
Branch > Compare to Branch を選択。
そうすると、以下のようにそれぞれで差分を確認することができます。

コマンド
コマンドで使うことが多い気がします。
GitHub Desktopで表示するより細かい指定ができて便利だからです。
git diff ~~比較先を記入~~
例1:最新コミットとの差分
git diff HEAD~1 HEAD
例2:任意のコミット同士
git diff コミットID コミットID
このようにいろんな差分を入力した内容同士で比較や確認ができるのでコマンドの形でよく使いますね。
概要
Git上のファイル、コミット、ブランチ単位で差分を確認できます。
前述したとおり、開発中は大体PR作成してGitHub上で差分を確認できるので有用性を感じる場面が少ないと考えます。
こいつが光るのは、Cherry-pickやMerge後など取り込み作業でミスがないかどうか確認するときに使えます。
例として「このブランチでは動くのにこっちはバグるな~」というときは
git diff バグが起きているブランチ 正常なブランチ
で比較し、取り込めていないコミットや差分を発見でき、MergeしたりCherry-pickで回収するなどの対応が取れます。
他にもリリース作業中に作業ブランチとリリースブランチの差分を確認して取り込めているか確認も可能ですし、特定のコミットだけCherry-pickした記憶あるけど本当に入れたっけ?とわからなくなった場合も確認できます。
この機能としてはGit操作の監査ツールとして使うとわかりやすくシンプルでよいです。
5. Squash Commits
開発中コミットを小刻みにすることがあります。
fix1
fix2
fix3
レビュー指摘修正
上記のようなコミットがそのまま残っていても内容が分かりづらいですね。そもそもタイトルには内容を書けという話ですが。
こういった場合、関連するコミットを1つにまとめることで、履歴を整理できるというのがこのSquash Commitsです。
UI操作
Historyタブでまとめたいコミットをドラッグし、合わせたいコミットに移動させる。
すると、Squashのポップアップが出るのでSummaryとDescriptionを入力し、ボタンを押下。
すると、Commit同士がまとまります。
コマンド
こちらは少し作業が必要です。
まず以下のコマンドを入力します。
git rebase -i 指定
最新のコミットから5つを表示してみると、
git rebase -i HEAD~5
pick abc1234 fix1
pick bbc1234 fix2
pick cbc1234 fix3
pick dbc1234 fix4
pick ebc1234 fix5
という表示が出ます。
上が古くて下が新しいコミットです。
さてこの状態で先頭のpickという文字をsquashに変更します。
pick abc1234 fix1
squash bbc1234 fix2
squash cbc1234 fix3
pick dbc1234 fix4
pick ebc1234 fix5
その後コミットメッセージを記載し保存して閉じ、履歴を確認すると、
pick fbc1234 fix123(fix1 + fix2 + fix3がまとまったCommit)
pick dbc1234 fix4
pick ebc1234 fix5
という表記になっており、まとまっているのがわかります。
ここで行われているのは先頭の要素を基準に合わせるという処理です。
注意点として仮に
squash abc1234 fix1
squash bbc1234 fix2
pick cbc1234 fix3
としてしまうと、以下のようなエラーが発生します。
error: cannot 'squash' without a previous commit
error: cannot 'squash' without a previous commit
You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
Or you can abort the rebase with 'git rebase --abort'.
Squash自体が前のコミットにくっつけるというものなので、
このコミットの前にコミットがないけど何にくっつければいいの???
という状態になりエラーするというわけですね。
他にもfixupとdropと変更できますが今回は省略です。
詳しくはこちら
概要
こちらのsquashは、履歴をいじっています。
はい。ということはforce pushしてます。
履歴を修正できて整理することが好きな人にとっては絶好の処理だといえますが、だからと言って開発中に行うのはお勧めしません。
状況としては、大きな実装が終了してひと段落したときに履歴を整理したり、
ブランチ間のコミットをCherry-pickで大勢移動させたい場合も、あらかじめコミットIDを集めておけば一つずつ取らなくていいので事故を防げます。
こちらも監査のために使うことをお勧めします。
まとめ
今回はGitの少し踏み込んだ領域の処理をまとめました。
自分もGitに詳しいほうではないので今回のものに関しては調べながらまとめましたが、Gitにとっての履歴を直接触るという行為の怖さと有用さを同時に摂取できたと感じています。
Gitは一見シンプルに見えて設定やコマンドがかなり多く、普段使いでは使わなくてもいいけど知っていると行動の選択肢としての幅が広がるなという印象でした。
今後も開発に携わる以上、Gitとは切っても切れない仲になります。せっかく設定としてあるなら知るだけでも価値があるなと考えているのでこれからも気づきがあればまとめていきます。
お知らせ
技術ブログを週1〜2本更新中、ソーイをフォローして最新記事をチェック!
https://qiita.com/organizations/sewii









