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でのマージを強制できない。

 

Git – ブランチ – 基本操作 – fast-forward

概要

  • ブランチをつくってコミットした内容は、他のブランチに影響しない
  • ブランチ作成後に新たなコミットがない状態で他のブランチの作業結果をマージすると、その結果が取り込まれて作業後にHEADが移動する
  • このようなマージをfast-forwardという(単に他のブランチの作業後まで早送りしているイメージ)

準備

確認用にbranchリポジトリーを作ってclone。

ワーキングディレクトリーに移動してログを確認。

mainブランチでイニシャルコミットが実行された状態。これを図にすると以下のような状態。

新たなブランチの作成

以下のコマンドで、新たなブランチtopicを作成する。

git branch ブランチ名

また以下のコマンドでブランチの状態を表示させて、作成結果を確認。

git brranch

新たにtopicブランチが作成されているが、現在はmainブランチにいることが確認できる(main*が付いている)。

ブランチ作成直後のログは以下の通り。

内容は変わっていないが、HEADmainなどに加えて新たにtopicも指している。

ブランチの移動

作成したtopicブランチに移動する。指定したブランチに移動するには以下のコマンドを使う。

git checkout ブランチ名

get branchtopicブランチに移動したことがわかる。

topicブランチでログを見ると、mainの内容が引き継がれていることがわかる。

ブランチ内での変更と独立性

ブランチ内での変更

現在いるtopicブランチで、topic.txtファイルを新規作成してコミットする。

ログを確認。

作成したファイルに、さらに変更を加えてコミット。

ログを確認すると、2つのコミットが反映されている。

topic.txtの内容も2つのコミットの内容が反映されている。

ブランチの変更の独立性

ここでmainブランチに戻ってみると、まだtopicブランチで作成・更新したtopic.txtは反映されていない。

mainブランチのログも初期状態のまま。

ここまでの状態は以下のようになる。mainブランチの状態は変わらず、topicブランチでコミットされてHEADが移っている。

mainブランチでのマージ

以下のコマンドは、現在いるブランチに指定したブランチの作業内容をマージする。

git merge ブランチ名

mainブランチにいる状態で、topicブランチでのコミットをmainブランチにマージする。

1つのファイルが更新され(新規作成を含む)、挿入が2件あることが示されている。

mainブランチでtopic.txtを見てみると、topicブランチでの新規作成・更新の作業が反映されている。

ログは以下の通り。なお、この内容はtopicブランチで確認しても全く同じになっている。

  • topicブランチでの作業がそのままmainブランチに適用されている
  • HEADmaintopicの両方のブランチの最後尾を指している

ここまでの状態は以下のようになる。mainブランチにtopicブランチのコミットが吸収されて、HEADの位置が最後尾に移っている。

fast-forward

上記のマージでは、topicブランチでの作業結果がそのままmainブランチに取り込まれ、HEADがその最後尾に進んでいる。

このようなマージをfast-forward (早送り)と呼ぶ。

マージの取り消し

ここで、mainでマージしたtopicの内容を取り消すことを考える。

取り消しできないコマンド例

git merge --abortは競合が発生したときのみ機能するが、今回は競合が発生していないので取り消しできない。

get reset --hard HEADは、マージが競合なく進んでHEADがマージ後の最新の位置に移動しているのでマージを取り消しできない。

コミット番号を指定したマージの取り消し

git reset --hard コミット番号で1つ前のtopic-1のコミット番号を指定すると、そのtopic-1の適用直後まで戻すことができる。作業結果も巻き戻されている。

ログの確認。HEADが1つ前のtopic-1の終了時点となっている。

ここでtopicブランチに移ってログを確認すると、その内容は変わっておらず2つのコミット結果が残っている。

ここでの状態は以下のようになる。マージされた1つのコミットが2つに分かれている。

さらにmainブランチで、イニシャルコミットの直後までリセットする。ファイルの作成前まで巻き戻されている。

ログもマージ前に戻る。

ここでもtopicブランチでのコミット結果は変化しておらず、状態は以下のようになる。

再マージ

topicブランチのマージを再度実行。この場合はtopicブランチでの2回のコミット結果が一挙に反映される。

mainブランチのログにも2度のコミットの記録が残る。

以下の状態に戻る。

ブランチの削除

ブランチでの作業履歴が不要になった場合は、以下のコマンドでブランチを削除できる。

git branch --delete ブランチ名
git branch -d ブランチ名

 

Git – マージ・競合

リモートリポジトリーに対する変更の競合とマージ

リモートリポジトリーの作成とクローン

リモート(GitHub)にリポジトリーを作成し、README.mdの内容を編集。

README.md : Test for conflict and merge

ローカルのuser1、user2ディレクトリーにリモートの内容をクローン(2人のユーザーが並行して作業するイメージ)。

競合がないファイル追加

ユーザーによるファイルの追加

user1によるファイル追加・コミット・プッシュ

この結果、GitHub上でsample1.txtの追加が確認できる。

別ユーザーによる別ファイルの追加

user1によりファイルが追加された後、user2によりファイルを追加・コミットし、プッシュしようとするとエラーになる。

リモートで他の作業が行われためと通知され、プルすることが提案されている。

ローカルでのマージ

user2でリモートからプルすると、エディター画面が開いて以下の様に表示される。

エディターを抜けてコマンドラインに戻り、プルの実行過程を確認。

ワーキングディレクトリーを確認すると、user1が追加したsample1.txtが追加されている。

マージ後のステータス

マージ後のステータスは、2つのコミット(sample2.txtの作成とマージ)がされていて、プッシュが提案されている。

ログで確認した結果。

コミットのプッシュ

2つのコミットをプッシュ。この結果、GitHub上でもsample1.txtsample2.txtが反映される。

同一ファイルへの変更の競合

ユーザーによるファイル編集

ワーキングディレクトリーuser1でプル。

user1としてREADME.mdの内容を編集。

編集結果をステージング、コミット、プッシュ。

この結果、GitHub上でREADME.mdの内容が変更されているのを確認。

別ユーザーによる同じファイルの編集

user1によりREADME.mdが編集された後、user2により同じファイル編集し、コミット、プッシュしようとすると、ファイル追加の時と同じようにエラーになる。

ローカルでのマージに競合

user2で編集した内容はそのままでリモートリポジトリーからプル。README.mdをマージしようとして、競合が発生したため、競合を解消してコミットするよう示唆している。

README.mdの内容を確認すると、user2の変更内容の反映を試み、その下に競合内容も追加されている。

手動で競合を修正してプッシュ

ファイルを修正し、コミット・プッシュ

README.mdをマージした結果はGitHub上で確認できる。