Git操作失敗談 あれこれ

この記事はプログラミング学習者がアプリ開発中に躓いた内容を備忘録として記事におこしたものです。内容に不備などあればご指摘頂けると助かります。

# 失敗談１：mainブランチが消えてしまった
Github上にリモートリポジトリを新規作成し、ローカルでも対応するリポジトリを作成しました。
git initしてgitによる管理をスタート
その後にgit checkout -b branchAとしてmainブランチから移動して作品を作っていたのですが、
途中でmainブランチに戻ろうと思って、git checkout main と実行したところ、
```git:エラー文
error: pathspec 'main' did not match any file(s) known to git
```
というエラーが発生してしまいました。
git branchをしてもブランチは何も表示されませんでした。
原因としてはgit init した後にgit cloneでリモートリポジトリの内容を複製していなかった事が挙げられます。

対処法として以下のコマンドを実行しました。
git fetch origin でリモートリポジトリの内容を取得
git checkout -b main origin/main でリモートリポジトリのmainブランチの内容を元にローカルリポジトリにmainブランチを作って移動する
ここで元々いたbranchAがmainブランチに変わってしまい、リポジトリ内のファイル類もリモートリポジトリの内容とbranchAの内容が混ざってしまいました。
ここでgit branch -m main branchA でmainブランチを branchAに書き換えました。
その後に再度下記のコマンドを実行
git fetch origin
git checkout -b main origin/main
ここでgit branch を実行するとmainとbranchAの２つのブランチが出現して望んでいた内容になりました。
ただし、ここで作成されたmainとbranchAが完全に同期してしまいました。
例えば、mainブランチでgit stash -u を実行して~~コミットできる内容~~未コミットの変更点を一時的に退避すると、branchAでも同じよう内容がgit stasu -u を実行されたて退避された状態になってしまいました。

この問題に対して、
git checkout branchA-backupとして、新しいブランチを作成した後に
git stash pop stash@{0}で一時退避していたファイルを復帰させました。
git add .
git commit -m "コミットメッセージ"
git push -u origin branchA
と、一度リモートリポジトリにPushしてコミット歴を作りました。
-uオプションではローカルリポジトリのbranchAとリモートリポジトリのbranchAの間にトラッキング関係を作ります。
この処理をすることで、ローカルリポジトリ内のmainブランチとbranchAの中身が区別されるようになりました。

:::note warn
冒頭にも記載しましたが、この問題の根本的な原因は初期段階でgit cloneを実行していなかったことです。
:::
git cloneをしなかった経緯ですが、リモートリポジトリを空の状態で作ったので、git cloneしてデータを継承する必要性を感じなかったことが挙げられます。
今後の学習に活かしていきたいと思います。

# 失敗談2：リモートリポジトリのmainブランチに直接Pushしてしまった。
これはうっかりやってしまったのですが、うっかりやってしまったにしては大きなミスだと認識しています。
ちょっとした成果物を完成させて、PRを通してコードレビューを受ける為にリモートリポジトリへPushする時の話です。

通常であればリモートリポジトリのmainブランチへは直接Pushせずに開発用の別ブランチを作成して、そこからリモートリポジトリへ同じ名前でPushして、Pull Requestを作成し、レビューを受けてからmainブランチにMergeするというのが基本的な流れです。

mainブランチへ直接Pushしてしまうということは内容が間違っていたり、不具合の可能性を孕んでいてもお構いなしにMergeしてしまうことを意味しており、とても危険な行為です。
今回の場合、リモートリポジトリは空の状態でしたので、コンフリクトなどは発生しないのですが、複数人で行なっている開発案件などで同じ行為をしたことを想像するとゾッとします。

以下はmainブランチへPushしてしまった後に実行した内容です。
mainブランチ
git update-ref -d HEAD　初回コミットの内容を取り消す
※このコマンドは初回のコミットを取り消すだけでなく、現在のブランチ(ここではmain)の履歴を全て削除します。初回コミットを取り消さずに修正する場合はgit commit --amendが推奨されます。
git rm --cached ファイル名　リモートリポジトリのmainブランチにPushしてしまった内容を削除する
~~※ --cachedをつけることでリモートリポジトリのファイルだけを削除する。~~
※ローカルリポジトリのインデックス(ステージングエリア)からファイルを削除する操作となり、リモートリポジトリへ反映させるにはcommit、push が必要となります。
このオプションが無い場合はローカルリポジトリの同名ファイルも削除してしまうので注意が必要
git add .
git commit コミットメッセージ
git push origin main Pushまですることで削除の操作をリモートリポジトリに反映できる
※git update-ref -d HEADやgit rm --cachedを実行して、ローカルとリモートのリポジトリのコミット履歴が一致しないので、--force (-f)オプションが必要になります。
git stash -u ローカルリポジトリのmainブランチにあるファイル類を一時退避
git checkout -b calender
git add .
git commit -m コミットメッセージ
git push origin calender リモートリポジトリにcalenderブランチを作り、Pushする
ここでリモートリポジトリのmainブランチへcalenderブランチからPull Requestを出そうとしましたが、下記のエラーメッセージが出ました。
```github:github上のエラーメッセージ
There isn’t anything to compare.
main and calender are entirely different commit histories.
```
mainブランチとcalenderブランチは全く異なるものなので、比較できません。という内容
※この時、リモートリポジトリのmainブランチは空の状態でした。
git update-ref -d HEADを実行して真っさらなmainを作り直したことが原因として挙げられます。

この時、以下のコマンドを実行することで、ローカルリポジトリの各ブランチとリモートリポジトリの各ブランチの関連状況を確認しました。
ローカルリポジトリのcalenderブランチはリモートリポジトリと関連付けできていないことが下のコマンド結果から確認することができます。
```
git branch -vv
* calender d0a8d39 first commit
  main     fd4745e [origin/main] Remove files from remote repository
```

次に下記のコマンドを実行することでリモートリポジトリとローカルリポジトリのcalenderブランチを関連づけることにしました。
```
git branch --set-upstream-to=origin/calender calender
```
この後、関連付けの確認をしたところ、意図した通りに設定することができました。
```git:calenderブランチにリモートリポジトリのcalenderが関連づけられた
git branch -vv
* calender d0a8d39 [origin/calender] first commit
  main     fd4745e [origin/main] Remove files from remote repository
```
ただし、これらのコマンドを実行してもエラーは変わらず表示されてしまいました。
```github:依然としてgithub上にエラーメッセージが表示される
There isn’t anything to compare.
main and calender are entirely different commit histories.
```
calenderブランチに対して関連付けを行いましたが、状況は改善されないままです。
ここでエラー文からmainブランチとcalenderブランチの履歴が完全に異なっていることが原因と考えました。

次に以下の順番でコマンドを実行しました。
```git:mainブランチにリモートリポジトリにあるmainブランチの内容(何も無い)をPull
git checkout main
git pull origin main
```
２行目の`--allow-unrelated-histories`オプションは共通の祖先を持たないブランチ同士のマージを可能にします。これにより独立して開始された２つのプロジェクトの履歴をマージすることができます。
gitは指定されたブランチ(下の例ではmain)の履歴と現在のブランチ(calender)の履歴を比較します。
共通の祖先がない場合でも両方の履歴を組み合わせて新しいマージコミットを作成します。
```
git checkout calender
git merge main --allow-unrelated-histroies
```
この後に、
```
git push origin calender
```
を実行しました。特にファイルは変更していなかったので、git addとgit commitは未実施。
上記のコマンドを実行したところ、mainとcalenderの間でコードの比較ができるようになり、Pull Requestをオープンできるようになりました。

:::note warn
そう言えば、この問題でも初期段階でgit cloneを実行していなかったような...
:::

上記２つの失敗談ではリモートリポジトリを作成しても中身は空で後から中身を作り上げていくという状況でした。今まではローカルリポジトリの中に重要なデータが入っていることも多く、`git clone`するのが当たり前のよう作業していましたが、今回のようにリモートリポジトリが空だと`git clone`の必要性を感じず、実行を省いてしまう結果となってしまいました。
これは言い訳ですね。

今回の教訓を受けて、`git clone`の実行を怠らないようにしたいと思います。

また、リモートリポジトリのmainブランチへ直接PushしないためにはGithub側でProtection Ruleを設けることで防ぐことができます。
うっかりの対策としても機能するでしょう。