Git – revert – 取り消しコミット

概要

“revert”は「元に戻す」意味。

  • git revertはコミットの内容を元に戻すためのrevertコミットを実行する
  • それまでのコミットの履歴を残したまま、その内容を打ち消す

準備

Git – タグで使った以下のリポジトリーを流用。

HEAD位置のコミットの内容を確認。commit-3でファイルに1行追加している。

sample.txtの内容も確認しておく。

revert実行

git revertでHEAD位置のコミットを指定。

git revert HEAD

実行するとエディターが起動し、revertコミットのデフォルトメッセージが表示される。

このままの内容で保存・終了すると、git revertの実行結果が表示されている。

ログを確認するとcommit-3は残ったままで、その後にRevert "commit-3"コミットが実行されている。

ファイルの内容はcommit-3の実行前に戻っている。

revertのrevert

revertコミットをさらにrevertすると元に戻る。

ログにも2回のrivertコミットの記録が残る。

sample.txtの内容も元に戻っている。

飛び越しはできない

冒頭のログの状態で、直前のコミットの1つ前のコミットを直接revertしようとするとエラー。

1つずつ遡るのは可能

まず直前のコミットをrevert。

ログ確認。

その上でcommit-2(ver_1.1タグ)のコミットをrevertは可能。

ログには2回のrevertコミットが残っていて、コミットを遡りながら1行ずつ追加された行を削除している。

ファイルはcommit-2実行前の状態に戻っている。

 

Git – タグ

準備

GitHubに空のリポジトリーを作ってクローン。

軽量タグ(lightweight)

コミット

ファイルを1つ作成してコミット。

コミット後のログ。

タグ付け~git tag ...

HEADがあるコミットにタグをつけるには以下のコマンド。

git tag タグ名

ログにタグが反映された。

注釈付きタグ(annotated)

コミット

ファイルに変更を加えてコミット。

タグ付け~git tag -a ... -m ...

このコミットに注釈付きタグをつける。

git tag -a タグ名 -m "注釈"

ログでの表示は軽量版と同じ。

タグの一覧

タグ名のみ~git tag

タグ名のみの一覧はgit tagで表示できる。

注釈付き~git tag -n

タグの注釈も含めて表示するには以下のコマンド。軽量版はコミットのコメントが表示される。

git tag -n

タグの削除~git tag -d

タグ名を指定して削除するコマンド。

git tag -d タグ名

タグ一覧で削除の確認。

ログで削除の確認。

過去のコミットへのタグ付け

特定のコミットにタグをつける場合は、コミットのチェックサム(一部でもよい)を指定する。

git tag タグ名 チェックサム(の一部)

タグの追加の確認。

タグの詳細表示~git show ...

タグの突いたコミットの詳細を見るのにgit showでコミットのチェックサムを指定する代わりにタグ名を指定できる。注釈付きタグの場合は、タグに関する情報も表示される。

リモートへの反映~git push --tags

単にgit pushしただけではタグは反映されない。

上部のtagsが0のまま。

タグをリモートに反映させるには、以下のように--tagsオプションをつける

git push --tags

ただしgit push --tagsはタグのみをプッシュする。

tagsが2となっている。

tagsをクリックするとタグ一覧が表示され、そのコミットに飛んだりzipをダウンロードできる。

Git – amend – 直前のコミットの修正

概要

git commit --amendは、直前のコミットの内容を修正する。

  • 直前のコミットが対象→2つ以上前のコミットは修正できない
  • コミットメッセージの修正
  • ファイルなどのコミット追加→ファイルなどの削除はできない

参考サイト:コミットの修正には git commit –amend が便利

準備

以下のローカルリポジトリーexampleを使う。

ファイル作成とファイル修正の2つのコミット。

コミット後のファイルの内容。

直前のコミットのコメントを修正する

エディターでコメント修正

以下のコマンドを実行するとエディター画面が開く。

git commit --amend

エディターに現在のコミットメッセージが表示されているので、必要な内容に変更する。

以下のように修正して保存・エディター終了。

エディターを終了すると、コマンドラインに実行結果が表示されている。

ログを確認すると、変更が反映されている。

コマンドラインでコメント設定

-mオプションでコミットメッセージを指定すると、エディターは起動せず、指定した内容でメッセージが書き換えられる。

git gommit --amend -m "メッセージ"

ログで確認。

直前のコミットに追加修正する

既存ファイルを修正~メッセージ変更なし

以下のコマンドは、コミットメッセージの編集をしない。ステージングされている操作があればコミットに追加される。

git commit --amend --no-edit

たとえばsample.txtにもう1行追加する作業を忘れていて、これを最後のコミットに加えたいとする。

git addした後、amendを実行。

git log -pで確認すると、最後のコミットで2行追加されている。コミットメッセージは変わっていない。

新たなファイルを追加~追加とメッセージ変更

たとえば別のファイルsample2.txtを作成し、これも最後のコミットに追加したいとする。

git addでステージングし、amendを実行する。今度は--no-editではなく、-mオプションで新たにファイルを追加したメッセージに変更。

git log -pで確認すると、最後のコミットにsample.txtの2つの追加とsample2.txtの追加が全て盛り込まれている。

 

Git – リモート – push時エラー~no-fast-forward

概要

  • ローカルでの作業結果をリモートにpushしようとしたとき、その間に他のユーザーがリモートに変更を加えていると、エラーになりpushできない
  • そのようなときは、リモートからpullまたはfetch & mergeで変更内容をローカルにマージし、その結果をpushする

準備

リモートリポジトリーの準備

あらかじめ空のリモートリポジトリーexampleを準備しておく。以降、これに対してローカルで2人のユーザーを想定して変更を加える。

user1の作成とクローン

ローカルでuser1ディレクトリーを作成し、そこにリモートリポジトリーをクローンして移動。

ローカルのログ。

 

同様にuser2ディレクトリーを作成し、そこにもリモートリポジトリーをクローンして移動。

ログはuser1と同じ内容。

user1によるコミット・プッシュ

user1で新規ファイルを作成してコミット。

ローカルのログには新たなコミットが加わっている。

この内容でリモートにプッシュする。リモートには新たなコミットが追加されるが、user1側のローカルには反映されない。

user2によるコミット・プッシュ・エラー

現時点でuser2のローカルリポジトリーの内容がリモートと異なっている状態で、user2でファイルを作成・コミット。

user2のローカルのログでは、user1のコミットは当然反映されていない。

この状態でuser2からコミット結果をプッシュしようとすると、リモートリポジトリーがその後の作業結果を含んでいるので更新できない、とエラーになる。

対応処理

このようなエラーの場合、リモートの内容をgit pullするか、フェッチ後にマージする。ここではfetch & mergeで進める。

フェッチ

まずリモートの内容をフェッチ。

フェッチされたリポジトリーFETCH_HEADに移動。

フェッチされた内容のログ。user2クローン後のuser1による変更が記録されている。

マージ

mainブランチに移動

作業中のmainブランチにフェッチされた内容をマージ。このマージはno-faseet-forwardになるので、エディターが立ち上がる。

マージ後のログ。user1user2のコミットが含まれ、マージコミットが加わっている。

git log --graphでも確認。

プッシュ

マージされた内容で、改めてプッシュ。

リモートへの繁栄の確認

プッシュ後のリモートの内容をuser1でプル。

user2のコミットも反映されている。

ログも最終状態になっている。

 

Git – リモート – clone/push/pull/fetch

clone~リモートリポジトリーをローカルに複製

コマンド形式

git cloneはリモートリポジトリーの複製をローカルリポジトリーとして作成する。パスワード認証、SSH認証に対するコマンド形式は以下の通り。

パスワード認証

git clone https://github.com/account/remote_rep [local_rep]
SSH認証
git clone git@github.com:account/remote_rep.git [local_rep]
  • local_repを省略するとremote_repと同じ名前でディレクトリーが作成され、ローカルリポジトリーに設定される
  • パスワード認証はログインパスワードが非推奨になり、2段階認証化が必要
  • SSH認証の場合は別途キーセットの生成・登録などが必要

実行例

  • あらかじめリモートリポジトリーexampleを作成しておく
  • user1ディレクトリーを作成し、そこにexampleリポジトリーをクローン

push~ローカルのコミットをリモートに反映

コマンド形式

ローカルリポジトリー内でgit pushを実行すると、ローカルでのコミットをリモートに反映

git push

実行例

user1でcloneしたローカルリポジトリーのでファイルを作成・コミット。

コミット結果のログ。

コミット結果をリモートにプッシュ。

GitHub上で作成されたファイルが確認できる。

cloneはその時点でのリモート内容を複製

user2ディレクトリーを作成してexampleをクローン。

user1が作成したファイルが反映される。

リモートへのpushはfast-forward

user2ファイル作成・コミット。

コミット結果のログ。

user2のコミット結果をプッシュ。リモート上でfast-forwardでマージされる。この間にリモートで他のコミットが行われているとpushできない。

リモートリポジトリーからのpull

user1でリモートのexampleをプル。リモートの内容がfast-forwardでローカルリポジトリーにマージされる。

この時点までの変更(user2でのファイル作成)が反映されている。

ログも最新状態になる。

pullの後のコミットとpush

user1でファイルを変更、コミット。

コミット後のログ。

user1のローカルリポジトリーからリモートにpush

fetch~一時的なリモートの内容確認

フェッチの確認

user2git fetchを実行。

フェッチを実行した結果。

  • フェッチした場合はローカルリポジトリーにマージされず、FETCH_HEADブランチに複製される
  • FETCH_HEADは不可視でgit branchでは確認できないが、git checkoutで移動することができる
  • FETCH_HEADで変更・コミットした内容は最終的に破棄される
  • 破棄したくない場合はgit switch -cで新たなブランチを作成できる
  • この操作はgit switch -cで取り消せる

FERCH_HEADブランチのログ。

フェッチ内容の反映 = pull

FETCH_HEADがある状態でmainブランチに移動。

FETCH_HEADブランチをマージ。

FETCH_HEADで変更していなければ、結果は直接pullを実行したときと同じ。

 

Git – rebase – 競合あり

準備

Git – merge – no-fast-forward – 競合ありで準備したのと同じ内容でブランチとコミットを準備する。

mainブランチのファイル、ログの内容。

topicブランチのファイル、ログの内容。

準備後の状態は以下の通り。

rebase

リベースは、現在いるブランチの出発点を指定したブランチの末尾に変更する。ここではtopicブランチをmainブランチに繋げるため、topicブランチに移動する(あるいはtopicブランチにいることを確認する)。

topicブランチでmainブランチを指定してgit rebaseを実行。common.txtで自動マージを実行しようとしたときに競合が発生している。

common.txtの内容を確認してみると、マージの時と同じく競合部分の差分がマークされている。

ただし、マージで競合した場合(以下の内容)と違うところがある。

topicでログを見ると、ベース変更のためにmainブランチのコミットを適用したところで止まっている。

abort

競合が発生したリベースを中止したい場合は、git rebase --abortを実行。

ログを確認すると、rebase実行前の状態に戻っている。

競合の解消

再度リベースを試みる。

競合しているcommon.txtのマークされた部分を修正。

競合解消後のリベースの手順は以下の通り

  • 競合を解消したファイルをgit add
  • ここでコミットせずに、git rebase --continue

ところがまた競合が生じた。

common.txtの内容。今度はtopicブランチでの追加結果が反映されていて、上で新たに修正した内容と食い違っていると言われている。

common.txtを修正。

git addしてgit rebase --continue。今度は最後まで通った。

リベース後のtopicブランチのログ。コミットが繋がっている。

mainブランチの方は特に変更されていない。

リベース後の状態は以下のようになっている。

rebaseの撤回

リベースした後に取り消したい場合。git reflogで詳細なログを表示し、リベース開始直前の項目のログ番号を確認する。

rebaseを実行したブランチ(この場合はtopicブランチ)で以下を実行。間違ってmainブランチで実行すると、mainのコミット結果がtopic1topic2で書き変わってしまう。

topicログの結果。リベース前に戻った。

mainブランチは変更されていない。

リベース結果のmainブランチへの反映

topicブランチでリベースを実行すると、その結果はtopicブランチでのものとなる。最終的にmainブランチにその結果を反映させたいときは、mainブランチに移動してそこでtopicブランチをマージする。

mainブランチでコミットが行われていなければ、マージはfast-forwardで行われ、mainの内容がリベース後の結果まで送られる。

作業結果も反映されている。

マージ・リベースの撤回

マージの撤回

まずマージを撤回する。git reflogでマージ直前のログ番号を確認。

確認したログ番号でgit reset

mainブランチの内容が元に戻る。

topicブランチの内容はリベース直後の状態。

リベースの撤回

先ほどのリベースの撤回と同じ手順。

ログ番号を指定してgit reset

topicブランチの内容が元に戻っている(mainブランチも)。

 

Git – rebase – 競合なし

準備

Git – merge – no-fast-forward – 競合なしで準備したのと同じ内容でブランチとコミットを準備する。

mainブランチのファイル、ログの内容。

topicブランチのファイル、ログの内容。

準備後の状態は以下の通り。

rebase

リベースは、現在いるブランチの出発点を指定したブランチの末尾に変更する。ここではtopicブランチをmainブランチに繋げるため、topicブランチに移動する。

topicブランチでmainブランチを指定してgit rebaseを実行。

リベース後のログ。マージと違って、mainブランチにあったコミット群の後にtopicブランチのコミットが連なっている。

main1main2のコミット番号はmainブランチと同じだが、topic1topic2のコミット番号は、topicブランチでの番号とは異なっている。

topicブランチで、mainブランチのコミット結果も反映されている。

mainブランチの方は特に変更されていない。

リベース後の状態は以下のようになっている。

rebaseの撤回

リベースした後に取り消したい場合。git reflogで詳細なログを表示し、リベース開始直前の項目のログ番号を確認する。

rebaseを実行したブランチ(この場合はtopicブランチ)で以下を実行。はじめに間違ってmainブランチで実行して、mainのコミット結果がtopic1topic2で書き変わってしまった。

topicログの結果。リベース前に戻った。

mainブランチは変更されていない。

リベース結果のmainブランチへの反映

topicブランチでリベースを実行すると、その結果はtopicブランチでのものとなる。最終的にmainブランチにその結果を反映させたいときは、mainブランチに移動してそこでtopicブランチをマージする。

mainブランチでコミットが行われていなければ、マージはfast-forwardで行われ、mainの内容がリベース後の結果まで送られる。

作業結果も反映されている。

マージ・リベースの撤回

マージの撤回

まずマージを撤回する。git reflogでマージ直前のログ番号を確認。

確認したログ番号でgit reset

mainブランチの内容が元に戻る。

topicブランチの内容はリベース直後の状態。

リベースの撤回

先ほどのリベースの撤回と同じ手順。

ログ番号を指定してgit reset

 

Git – 削除したブランチの復旧

問題例

例として、mainブランチでtopicブランチのコミットをfast-forwardでマージし、その後topicブランチを削除。その削除を取り消してtopicブランチを元に戻したいようなとき。

参考サイト:削除したブランチは復活できる!

準備~ブランチの削除まで

リポジトリー・mainブランチの作成

GitHubであらかじめ準備したbranchリポジトリーをclone。

ログ確認。

topicブランチの作成・コミット

topicブランチを作成・移動して、ファイル作成・コミット。

topicブランチのログ確認。

topicブランチのマージ

mainブランチに移動し、topicブランチをマージ。

マージ後のファイルとログを確認。

topicブランチの削除

topicブランチを削除。

削除したブランチの復旧

ここで、削除してしまったtopicブランチを復旧したいとする。

git reflogでの確認

まずgit reflogで履歴確認。topicブランチが最終的に確定した部分を探す。

今回の場合、topic-commitHEAD@{2}がこれにあたる。

ログ番号を指定してブランチを復旧

以下のコマンドでブランチを復旧。

git branch ブランチ名 HEAD@{ログ番号}

復旧確認

topicブランチが作成されたこと、そのログやファイルの内容が元に戻っていることを確認。

 

Git – merge – no-fast-forward – 競合あり

概要

Git – merge – no-fast-forward – 競合なしでは、mainブランチとtopicブランチのそれぞれでコミットが行われるが、互いに干渉しない場合を考えた。そして、各ブランチにコミットがある場合にはfast-forwardではなく、no-fast-forwardマージになることを確認した。

競合がない場合には、no-fast-forwardマージにおいて、Gitが自動的にマージを行ってくれる。しかし互いのコミットに競合が生じる場合、Gitは競合を自動的には解消できず、マージも行われない。

このようなときは、Gitの示唆するファイルでの競合を解消してコミットし、手動でマージを完了させる。

準備

リポジトリーの準備

Git-ブランチ-基本操作-fast-fowardのリポジトリーを使う。イニシャルコミットの状態から始める

mainブランチとこれから作成するtopicブランチで並行して変更するファイル(common.txt)を作成する。

ファイル作成・コミット後のログを確認。

ブランチの作成

topicブランチ作成、そこに移動して、mainブランチで作成したファイルが反映されていることを確認。

ログ確認。topicブランチはmainブランチの直近の環境を引き継いでいる。

各ブランチでのコミット

topic/main各ブランチで並行して、common.txtに変更を加えていく。

topicブランチで行追加。

mainブランチで行追加。

topicブランチで既存の行を変更。

mainでブランチ同じ行を変更。

各ブランチのログを確認する。

mainブランチのログにはmainでの2つのコミットが追加記録されている。

topicブランチのログにはtopicでの2つのコミットが追加記録されている。

ここまでの状態は、以下のようになっている。

マージ

競合によるマージの中止

mainブランチからgit mergetopicブランチをマージ。しかし競合しているため自動マージができず、マージは行われない。

競合の解消とコミット

common.txtの内容を見てみると、競合する部分がGitによって示されている。

common.txtの内容を手動で修正、コミット。

マージ完了。ログで確認。

git log --graphで確認。

マージ後は以下のような状態になっている。

topicブランチでの状態は変化なし。

マージ撤回

以下のコマンドで、マージ直前の常態に戻る。

get reset --hard HEAD^

mainブランチのファイル内容、ログともマージ前に戻る。

再マージとabort

マージで競合した場合のabortの確認のため再度マージする。競合が発生している。

マージしようとして競合状態にある場合、以下のコマンドでマージ前の状態に戻せる。

gite merge --abort

ファイルの内容、ログともマージ前に戻る。

競合中のブランチ移動制約

競合が解消されない状態で、ブランチ移動が制約されることを確認する。まず、競合するブランチのマージを試みる。

競合が解消されていない状態ではtopicブランチには移動できない。

競合を解消する。

競合が解消されればtopicブランチに移動可能になる。

fast-forwardマージは不可

競合がないコミットの場合と同じように、no-fast-forwardのマージを強制的にfast-forwardで行うことはできない。

 

Git – merge – no-fast-forward – 競合なし

概要

  • ブランチをマージするとき、それぞれのマージでコミットがされているが、各コミットは互いに競合しない場合
  • このようなときは、Gitがその内容を解析して、マージコミットを発行してマージされる
  • この場合、マージ下でも作業が発生しているので、単なる早送り(fast-forward)にはならない
  • このようなマージをno-fast-forwardと呼ぶ

準備

リポジトリーの準備

Git-ブランチ-基本操作-fast-fowardのリポジトリーを使う。イニシャルコミットの状態から始める。

ブランチの作成

git branchコマンドで、topicブランチを作る。

各ブランチでの変更作業

mainブランチとtopicブランチで交互にコミット

まずmainブランチでmain.txtファイルを新規作成してコミット。

topicブランチに移動してtopic.txtを新規作成、コミット。

mainブランチに移動してmain.txtの内容を更新。

topicブランチに移動してtopic.txtの内容を更新。

状態確認

mainブランチの状態は以下の通り。

topicブランチの状態は以下の通り。

mainブランチとtopicブランチのコミットは独立に行われていて、互いに影響していない。

マージ

mainブランチでtopicブランチのコミットをマージ。

git merge topic実行直後にエディター画面になり、以下のように表示される。

末尾に「マージテスト」の1行を追加し、保存・終了(vi/vimエディターの場合は:wq)。

するとコマンドラインに戻り、mainブランチから見て1つのファイル(topic.txt)が更新(作成)され、2行挿入されていることが表示されている。

マージの結果、2つのファイルとその内容が反映されている。

ログを確認すると、時系列でmainブランチ、topicブランチでの各コミットが並び、最後にマージコミットも記録されている。

ログ表示のオプションで、ブランチごとのコミットがわかるよう、以下のコマンドで表示させてみる。

git log --graph

なお、この段階でtopicブランチのコミット履歴はそのまま残っている。

マージ後は以下のような状態。

mainブランチでは一連のコミットが時系列順に並べられるが、main/topic各ブランチでのコミットは識別されている。また、topicブランチでのコミット履歴も別に残っている。

マージの巻き戻し

効かない例

競合が発生していないのでabortは効かない。

もちろん、マージコミットの場所でも効かない。

失敗例

マージコミットの1つ手前のtopicでリセットすると、topicブランチでのコミットだけが残ってしまった。

mainブランチでのログ。main関係のコミットが消えてしまった。

topicブランチでのログ。こちらは変化なし。

正しい巻き戻し方

以下のいずれか。

reset --hard HEAD^
git reset --hard HEAD~

mainブランチのログ。mainでのコミットだけが残っている。

topicブランチのログ。topicでのコミットのみ。

マージ後のブランチ削除

再度マージする。

ログ確認。

topicブランチ削除。

mainブランチのログにはno-fast-forwardマージの記録が残る。

fast-forwardマージは不可

main/topic各ブランチでコミットがある状態では、fast-forwardでのマージを強制できない。