Magit の覚え書き

目次

1 はじめに

Magitは git操作をEmacs上から行う為のEmacsパッケージです。 ファイルを編集した後の差分を確認したりログの一覧を眺めたり コミットメッセージを 詳しく書いたりと非常に便利に使用させていただいてます。ただ、あまり慣れない操作を 行う場面に遭遇した時、「あれ?どうすりゃいいのこれ?」ってなる事があったので、 備忘を兼ねた個人的なメモを作成してみました。

Magitからは殆ど全ての git操作を行えるそうですが、本文書では全ての操作を網羅 している訳ではありません。また、magit-statusを使って任意のgitリポジトリに アクセスできている所からを前提としているので、インストール操作や gitリポジトリの作成などの操作については特に説明していません。

2 環境について

本文書の作成に当たって 操作確認等に使用したOSやソフトなどは以下の通りです。

  • OS : Windows10 Pro 1909
  • Emacs : Emacs 26.3 (自前IMEパッチを当てて 32bit Cygwin(3.0.7)で野良ビルドし GUI(emacs-w32.exe相当)で使用)
  • Magit : MELPAでインストールした magit-20190418.1642
  • git : version 2.21.0 Cygwinでパッケージインストールしたもの

Magitは2019年12月現在で比較的新しいバージョンを想定しています。 例えば magit-20141228.1413 以前だとショートカットが大分異なるので、 本文書を参考にする場合は「magit-20190418.1642 に対応する magit-20141228.1413での操作は.....」を各自で確認する必要があるかと思います。 また、magit-20141228.1413より新しいバージョンでもショートカットや操作体系 が異なっている可能性がある事をあらかじめご承知おきください。

Emacsは様々なビルドやバージョンが存在し、様々なプラットフォームで動作する上、 様々な個人設定が行われている可能性があります。各自で自身の環境を ご確認いただければと思います。

3 表記について

本文書内での表記について。

  • Emacsでのキー表現"RET"は「リターンキー」と表記します。キーボード的には「エンターキー」の事です。 リターンキー(エンターキー)を押す事を"リターンする"もしくは単に"リターン"と表記しています。

  • Emacsでのキー表現「C-c」は Ctrlキーを押しながらcキーを押す意味で表記します。

4 こんな時こうする

本題です。たまにしか行わない操作の場合、「どうすりゃいいのこれ?」と思ったり、 「こうかな?」と色々操作してしまうと元に戻せない状態に陥る場合がありました(^^; そこで、「この場面で こうしたくなったら こうすればよい」を記しておく事にしました。

4.1 状態遷移図

変更を加えて共用リポジトリにpushするまでにやりたくなりそうな事を状態遷移図に示してみました。

magit_op_flow.png

ある状態から所望の状態に遷移するのに必要な操作を順にまとめてみます。

4.2 変更を加える

既にgit管理下に置かれているファイルに変更を加えて「g」(refresh current buffer)で再表示すると 「Unstaged changes」の一覧にファイルが表示されるようになります。

4.3 変更を戻す

変更を戻す方法はいくつかあります。

  • Discardでファイル毎に変更を戻す
    Unstaged changesのチェックアウトし直したいファイルにカーソルを合わせた状態で、 「k」(Discard) を入力すると "Discard unstaged changes in カーソルを合わせたファイル名? (y or n)" と問われるので"y"を入力すると変更した内容が元に戻ります。

  • 変更前と同じになるように編集し直す。
    変更が全て戻せた場合、「g」(refresh current buffer)で再表示するとUnstaged changesから 表示が無くなるハズです。

  • git reset でファイルをチェックアウトし直す。
    Unstaged changesのチェックアウトし直したいファイルにカーソルを合わせた状態で、 「X」(Reset), 「f」(a file) の順で入力すると "Checkout From revision (default master):" で入力を促されるので そのままリターン、続いて "Checkout file (default ファイル名):" で入力を促されるので、defaultと同じなら そのままリターンで変更前のファイルがチェックアウトし直されます。

4.4 ステージする

以下の手順でステージします。

  • 指定した変更をステージする。
    「Unstaged changes」のステージしたいファイルにカーソルを合わせて 「s」(Stage) を 入力するとカーソル位置のファイルが「Staged changes」に表示されるようになります。

  • 全ての変更をステージする
    「Unstaged changes (N)」の行にカーソルを合わせて 「s」(Stage) を 入力するとUnstaged changesに含まれる全てのファイルがステージされます。

ステージについての補足
gitには概念上「変更を加えてコミット対象にする(まだコミットはされていない)」状態を "ステージされた状態"と言う事があります。ただ、gitコマンド上は git add で行うのですが Subversionなどで管理下に置かれていないファイルを管理下に置く svn add と同じ意味 だと思っていると、話が通じない事があるかも(Subversionの体系だと「add済のファイルを 更新したらまたaddするってどういう事?」となるかも)知れません。 Magitでは ステージされたファイルは「Staged changes」に表示され、コミットは Staged changesに含まれるファイルを対象にするので、状態がうまく表現されていると 思います。

4.5 アンステージする

以下の手順でアンステージします(ステージをやめる)。

  • 指定した変更をアンステージする。
    「Staged changes」のアンステージしたいファイルにカーソルを合わせて 「u」(Unstage) を 入力するとカーソル位置のファイルが「Unstaged changes」に表示されるようになります。

  • ステージされた全ての変更をアンステージする。
    「Staged changes (N)」の行にカーソルを合わせて 「u」(Unstage) を 入力するとStaged changesに含まれる全てのファイルがアンステージされます。

4.6 ステージ後に変更を加えた

厳密にはステージした直後の変更は「Staged changes」に含まれ、ステージ後に追加で加えた変更は 「Untaged changes」に含まれた状態になります。

起こり得る状況としては あるファイルに対して、

  1. ひとまず「機能Aの修正」が完了したので ステージした。
  2. 事情により「機能Aの修正」がすぐにコミットできない。
  3. 2.で「機能Aの修正」はコミットできないが、「機能Bの追加実装」を進めたい。

という場合が考えられます。

対応方法としては以下のような感じになるかと思います。

  • 「機能Aの修正」と「機能Bの追加実装」をまとめてコミットする事にした。
    追加で加えた変更が「Unstaged changes」に含まれている状態で 「s」(Stage)によりステージすれば、「Staged changes」に含まれている変更が上書きされます。 マージではないので「機能Aの修正」が入った状態で「機能Bの追加実装」を行わなくては いけません。

  • 「機能Aの修正」と「機能Bの追加実装」を分けてコミットする事にした。
    「機能Aの修正」をコミットした後に、「機能Bの追加実装」をステージしてコミットする事になるでしょう。

4.7 コミットする

ステージされた変更をコミットします。

  • 「c」(Commit)、「c」(Commit) の順で(即ち「c c」とcを二回)入力するとコミットメッセージの入力バッファが 開きます。メッセージを入力後「C-c C-c」を入力するとコミットされます。「C-c C-k」を入力するとコミットは キャンセルされます。

4.8 コミットを取り消す(Reset/Revert)

コミットを取り消す方法はいくつかあります。

  • git reset でコミット自体を無かった事にする。
    「X」(Reset)、「s」(soft)の順に入力。 "Soft reset master to (default master): "で入力を促されるので "HEAD^"と入力します。 「 git reset --soft HEAD^ 」相当の操作が行われます。
    #デフォルトのままリターンすると何も変化が無いので「なんだこりゃ?」となりました(^^;

  • git revert でコミットを打ち消すコミットを行う。
    「V」(Revert)、「V」(Revert commit(s) の順に入力。 "Revert commit (default master): "で入力を促されるのでそのままリターンで、 コミットメッセージの入力バッファが開きます。テンプレメッセージが入力されているので 必要に応じて変更し(必要なければそのままで)、「C-c C-c」でコミットが行われます。 最初のコミットとrevertのコミットの二つのコミットを共用リポジトリにpushすると結果的に 変更されなかったのと同じ状態になります。ただ、ログには打ち消した事が記録として残ります。

    git revertの補足
    変更の打ち消しは、実際には「変更前のコードをステージしたのちコミットを行う」という作業を 自動で行っている感じです。この為、git revertで生成した打ち消しコミットも、 「打ち消しコミットをgit resetで戻した後、戻しのコードをgit resetでチェックアウトし直す」 とすれば、打ち消しコミットも無かった事にできます。

「git revert」の方では共用リポジトリにもログが残ります。他の人の(結果的に)何もしてない事が いちいちログに残るのは少々邪魔な感じもします。 個人的には、何もしなかったのならば git reset を使う方法でログに残らない方が 良いのではないかと思います。

4.9 コミットコメントの修正

以下の手順でコミットコメントを修正できます。

  • コミットコメントの修正
    コメントを修正したいコミットにカーソルを合わせて、 「c」(Commit)、「a」(Amend) の順に入力。コミットコメントの編集バッファが開くので コメントを修正した後、「C-c C-c」で変更を確定します。コメントを変更した事自体は ログに記録されません。

    Amendの留意点
    Amendする際に「Staged changes」に未コミットの変更が置かれていると、コメントの 変更と共にステージされている変更もコミットに含まれてしまいます。 コミットに入れ忘れた変更であれば問題ありませんが、ただコメントを修正したいだけ であれば、一旦「Staged changes」の変更はアンステージしておくか、Stashを使って 現在の変更を一旦退避しておく必要があります。

4.10 コンフリクトの解消

便宜上「コンフリクト」という状態で表現しました。自分の変更を行ったファイルを 他の人が先に変更し共用リポジトリにpushされてしまうとコンフリクトします。 「Unstaged changes」、「Staged changes」、「Unmerged into XXX」のいずれかの 状態であれば、pullした時にコンフリクトが発生する可能性があります。

コンフリクトを解消しないと自分の変更を共用リポジトリにpushする事はできません。 コンフリクトを解消する方法はいくつかあります。

  • 未コミットの変更をStashで一時退避してからマージする。
    1. 「z」(Stash)、「z」(both)の順で入力すると "Stash message: On master: "で入力を促されるので何かしら退避内容文字列を入力して リターン。「Unstaged changes」および「Staged changes」が無くなり、 「Stashes」に追加されます。
    2. 退避した所で、Unpulled に並んでいるpullできなかったコミットにカーソルを合わせて 「m」(Merge)を入力した後、"Merge (default XXXX):"にリターンでマージされます。
    3. 引き続き退避した変更を取り出します。取り出したいstashにカーソルを合わせて 「z」(Stash)、「p」(pop) の順で入力すると "Pop stash stash@{x}? (y or n)" と聞かれるので y を入力すると退避していた変更が 取り出されますが、コンフリクトしている変更は「unmerged」となります。
    4. unmergedとなっているファイルを開くと、差分が記された状態となっています。 差分が記された箇所を意図する変更に書き換えます。 セーブすると unmergedとなっていたファイルは modified となります。 これでコンフリクトは解消された状態になります。
    5. 3. での Pop stash でコンフリクトしていると、Stashesからは消えずに残ったままに なります。マージ内容に問題無さそうであれば、「z」(Stash)、「k」(Drop)の順で 入力すると、"Drop stash stash@{x}? (y or n)" と聞かれるので y を入力すると 指定したstashは削除されます。

  • 未コミットの変更を全て捨てる。
    「Staged changes」を「アンステージする」で全て「Unstaged changes」にし、 「Unstaged changes」の状態のファイルを「変更を戻す」の手順で変更をしていない状態 に戻した所でコンフリクトは解消した状態になります。 再度pullし直すとコンフリクトする事無くpullできます。

  • まだpushしていない自分のコミットをマージする。
    1. pullした時に、コンフリクトしているファイルが「Unstaged changes」と「Staged changes」の 両方にリストされます。
    2. unmergedとなっているファイルを開くと、差分が記された状態となっています。 差分が記された箇所を意図する変更に書き換えます。 セーブすると unmergedとなっていたファイルは modified となります。
    3. 「Staged changes」に入った modifiedとなったファイルにカーソルを合わせて 「m」(Merge)、「m」(Merge) の順で入力(即ち「m m」とmを2回入力)すると マージコミットメッセージを編集するバッファが開くので必要に応じてメッセージを 編集したら「C-c C-c」でコミットします。
    4. 「Unmerged into XXX」には最初のコミットとマージコミットの二つがリストされた状態 となりコンフリクトは解消します。
      #その後二つのコミットをpushすればOKです。

  • まだpushしていない自分のコミットをマージせずに全て捨てる。
    1. pullした時に、コンフリクトしているファイルが「Unstaged changes」と「Staged changes」の 両方にリストされた状態になります。
    2. 「Unmerged into XXX」の自分のコミットにカーソルを合わせて「X」(Reset)、「h」hard の 順で入力すると、"Hard reset master to (default master):" と入力を促されるので "HEAD^"と入力します。「 git reset --hard HEAD^ 」相当の操作が行われます。 自分のコミットは全て無かった事となりコンフリクトは解消した状態になります。 再度pullし直すとコンフリクトする事無くpullできます。

5 ショートカット操作が判らないとき

Magitでは様々なgit操作が可能なのですが、可能(そう)な事は判っていても具体的にどうすれば その操作ができるのか判らない場合があります。

その場合は「?」(M-x magit-dispatch) からコマンドを確認しながら実行する事が可能です。 例えば コミット を実行する場合、「?」で 1次選択肢が表示されます。一覧の 'c Commit' にしたがって 「c」を押すと、引き続き2次選択肢が表示されます。ここでも 一覧の 'c Commit' にしたがって「c」を押すとコミットメッセージの編集画面が開くという 感じになります。

次回からは最初の「?」を省いて「c c」と押せばコミットが実行できるという事が判ります。 非常に沢山のコマンドがありますので、頻繁に行う操作については自然とショートカットが 覚えられるかと思いますが、滅多に使わない操作は「?」から確認しながら実行するのが 良いのではないかと思います。

6 操作履歴を確認する

Magitは git操作をうまく行ってくれるのですが、うまくいかない場合に gitの 生コマンドで何をやっていたのか知りたくなる場合があります。

その場合は「$」(M-x magit-process-buffer) を実行するとgitコマンドの実行履歴を 表示する事ができます。Failした場合などに所望の操作が行われていたのかを 確認するのが良いと思います。

7 おわりに

本文書は自分用に忘れそうな分だけメモるつもりで作成しました。 そんな訳であまり参考にならないかも知れませんが もし役に立つことがあれば幸いです。

8 履歴

  2019/12/22 : 初版。
  2020/02/21 : 変更を戻す方法にDiscadを使う方法を追記。

TOP PREV

著者: TANE

Emacs 26.3 (Org mode 9.3.6)