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上で確認できる。

 

GitHub – SSH接続

概要

GitHubにローカル(Vagrant/CentOS7)からSSH接続を設定するのに、けっこう躓いてしまったので整理。GitHubに公開鍵をセットするのは当然として、ローカル側でいろいろ設定しないといけない。

流れは最後の「まとめ」に整理したが、概ね以下のような留意点あり。

  • ssh-addで秘密鍵を登録する必要がある
  • ssh-addに先立ってssh-agentを実行する必要がある
    • eval `ssh-agent’で実行する必要がある
  • SSHで扱うリポジトリーは、SSHでcloneする必要がある
    • SSH接続用のURLの書き方がある

鍵の生成

ssh-keygenで秘密鍵・公開鍵を生成する。

GitHubへの公開鍵の登録

  1. ヘッダーメニューのドロップダウンからSettingsを選択
  2. 左メニューからSSH and GPG keysをクリック
  3. SSH keysの”New SSH key”ボタンをクリック
  4. 公開鍵を貼り付け

SSH接続までのトライ・アンド・エラー

ターミナルでのアクセス~エラー

ssh -T コマンドでアクセスしようとしたところエラーになった。

ssh-addしてもエラー

参考サイトGitHubに接続できないときの対処法より、ssh-addするがエラー

ssh-agentが必要

参考サイトssh-addできなかったときへの対処よりssh-agentを実行するがうまくいかない。

直接実行しても内容が表示されているだけらしいので、同サイトにあるとおり、eval `ssh-agent'を実行してキーが追加された。

ターミナルでアクセス

ssh -Tを改めて実行し、(GitHubはシェルでのアクセスができないが)正しく認証されたことを確認。

既存リポジトリーのcloneができない

ここでGitHub上に既に存在するリポジトリーをcloneしようとしたが、パーミッションで拒否される。

クローン段階からSSHでもエラー

先の参考サイトGitHubに接続できないときの対処法の後ろの方を読むと、httpsでcloneしたリポジトリーはsshでは扱えないらしい。

そこでhttps認証に倣って以下のコマンドを打ってみるがうまくいかない。

SSH接続用のURLで接続成功

参考サイトベビーサンになりたいに倣ってURLを書いてみると、やっと接続できた。

別のマシンへの鍵の複製

これまでの流れで導入した鍵を別のマシンへ複製し、GitHubへ接続。

~/.ssh/下にあるid_rsaid_rsa.pubの2つのキーファイルをもう1つのマシンの~/.ssh/下にコピー。この状態でssh -Tで認証が確認できた。

ただし、cloneやpull/pushなどのコマンドを実行するたびにパスフレーズの入力を求められる。

そこで、ssh-agentssh-addを実行する。

これで毎回パスフレーズを入力する必要がなくなった。

まとめ

ひとまず、以下の流れでSSH接続を整理。

  1. ローカルに秘密鍵と公開鍵を生成
  2. GitHubに公開鍵を設定
  3. eval `ssh-agent`を実行
  4. ssh-add ~/.ssh/id_rsaで秘密鍵登録
  5. ssh -T git@github.comで認証成功を確認
  6. 既存リポジトリーをSSHでclone
    • git clone git@github.com:ユーザー名/リポジトリー名.git
  7. 以後、ワーキングディレクトリー内でSSHでpull/push

鍵のペアーを他のマシンに複製して同じアカウントにアクセスする場合、

  1. id_rsaid_rsa.pubを他のマシンの~/.ssh/ディレクトリーにコピー
  2. eval `ssh-agent`を実行
  3. ssh-add ~/.ssh/id_rsaで秘密鍵登録

 

Linux – ssh-keygen – 鍵の生成

キーペアの生成

ssh-keygen -t rsa -b ビット数 -C "コメント"

生成されたキーは、デフォルトでは以下のファイルに記録されている。パーミッションは秘密鍵が600、公開鍵が644。

  • 秘密鍵:~/.ssh/id_rsa
  • 公開鍵:~/.ssh/id_rsa.pub

秘密鍵の内容

公開鍵の内容

オプション

ssh-keygen --help

参考サイト:

 

Git/GitHub – 基本的な操作

概要

GitからGitHubにプッシュ・プルする手順。最初にリポジトリーを作成する場合、GitHub上にあらかじめリモートリポジトリーを作成しておいて、これをローカルにgit cloneで取り込む。

パスワード認証について

パスワード認証が2段階になり、Personal Access Token (PAT)を用いるようになったため、リモートでGitHubに接続する際のパスワード認証ではPATを入力することとなっている。

Gitのデフォルトブランチの変更

Gitのデフォルトブランチ名はmasterだが、GitHubがデフォルトブランチ名をmainとしたことから、Gitでもmasterからmainに変更するよう設定。

手順

  1. GitHubで共有するクローンを空の状態で作成
  2. GitでGitHubのリポジトリーをクローン
  3. Gitでローカルの開発過程をコミット
  4. GitからGitHubにコミット結果をプッシュ
  5. 以後、リモートリポジトリーからローカルにプル

GitHubで新しいリポジトリーを準備

GitHub上に新しいリポジトリーを作成。

  • リポジトリー名:test
  • Add a README fileを選択
  • 内容は1行で"My very first repository"

リモートリポジトリーのクローン

ローカルのワーキングディレクトリーにGitHubのリポジトリーをクローン。

GitHubで作成したREADMI.mdファイルの内容が確認できる。

ローカルでの変更とコミット

ワーキングディレクトリーに新たにsample.txtファイルを追加する。

そして追加したファイルをステージング、コミット。

Laravelなどのフレームワークの場合、.gitignoreで指定されたファイルも併せて環境ごとpushするには、ステージングで以下のオプションを指定する。

git add -f .

GitからGitHubへのpush

git pushでコミットされた内容をプッシュ。

git push -u [URL] [branch]

[URL]はGitHubのリポジトリーのURLで、[branch]はリモートリポジトリーのブランチ。GitHubからcloneで複製したディレクトリー内の場合、git pushのみで実行可能。

コマンドを入力するとユーザー名とパスワードを聞かれるので、GitHubのユーザー名とパスワードを入力する。

これでローカルでの変更結果がGitHubのリポジトリーに反映され、GitHub上で変更結果を確認できる。

GitHubからGitへのpull

他で変更された可能性のあるリポジトリーで作業を開始する場合、リモートリポジトリーからプルする。

以下はGitHub側でREADME.mdの内容を編集した後に、ローカルでプルしている例。