めんどくさい上司に当たっても大丈夫なようにGitを勉強しよう✨

社会人経験0年、生まれてから嫉妬激励をあまり受けずに平凡に暮らしてきましたが、社会に出るとどんな方々がお待ちしているか正直わかりません。

なので今回は、いつもあやふやで適当に行っているGitの操作をちゃんと勉強していこうと思います。

レポジトリも用意したので、ご自由にフォークしてお使いください。

基礎的な話はQiitaなどでもいくらでも見かけるので、ある程度、リモートやローカルの知識がある前提だと助かります。

## 実行環境
* 昭和の頭脳
* 令和のパソコン
* 忍耐強さ

https://github.com/shimaf4979/auth-app

## リモート→ローカルに持ってくる
**上司：「これ↑使って作業するんだよ、早くローカルに持ってきて、ブランチは自分で作ってmainは汚すなよ！！🤛」**

はい、パワハラモラハラGitハラのスリーアウト三者凡退です。

まずは自分のパソコンにクローンで持ってきます。
```shell
# リポジトリをクローン
$ git clone https://github.com/shimaf4979/auth-app.git
# リポジトリのディレクトリに移動
$ cd auth-app
```
これで、Githubにあったmainディレクトリが写りました。

![スクリーンショット 2024-12-12 4.17.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3738967/ddf54a99-7906-6f77-f945-a5cf6b3e2e46.png)

今回はブランチを新規で作れということだったので作っていこうと思います。

現在のブランチを見ていきましょう。

![スクリーンショット 2024-12-12 4.21.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3738967/62e8e7fb-a63c-cb5c-ec8d-88398da3a1e4.png)

これをdevelopのものを引き継いだまま、新規のブランチを作っていきます。

考えられる主な方法は以下の二つですね。

![スクリーンショット 2024-12-12 4.32.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3738967/4e9b15bd-a331-da56-cbda-d877345fba7d.png)

基本的には左側のdevelopブランチを最新状態に更新してから新しいブランチを作成する方法が推奨される方です。

この方法は、ローカルdevelopが他の用途でも利用される可能性を考慮したうえで柔軟性が高いからです。

初期状態ではローカルにはdevelop等がいないので、リモートの状態を見てみましょう。

branchのコマンドは基本的にこの通りでしたね。

![スクリーンショット 2024-12-12 19.58.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3738967/028b0fce-6c60-c340-0122-baa5fde62e94.png)


```shell
auth-app % git branch 
* main

auth-app % git branch -r   
  origin/HEAD -> origin/main
  origin/develop
  origin/feature/client-waiting-list
  origin/main
```
これをローカルに引っ張ってきます。


![スクリーンショット 2024-12-12 10.03.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3738967/aea92a1a-9de1-8565-1ce6-6d67c4522841.png)

-vvはverboseの略で、詳細情報を出します。具体的にはローカルやリモートの結びつきなどです。

```shell
auth-app % git  checkout -b origin/shimaf4979 origin/develop
branch 'origin/shimaf4979' set up to track 'origin/develop'.
Switched to a new branch 'origin/shimaf4979'

 auth-app % git branch -vv
  develop           12e4bc1 first commit
  main              12e4bc1 [origin/main] first commit
* origin/shimaf4979 6b0c192 [origin/develop] 変更しました

```

これを、ローカルにはないのにもし新規で作成してしまった場合、リモートには結び付かれていないdevelopが存在しています。

```shell
auth-app % git  checkout -b develop
Switched to a new branch 'develop'

auth-app % git branch           
* develop
  main

auth-app % git branch -vv
* develop 12e4bc1 first commit
  main    12e4bc1 [origin/main] first commit
```



この場合、ブランチを消去して、やり直す方法とリモートとローカルを繋げる方法がございます。

消去する場合は他のブランチに移動した上で消しましょう。-dはcommitしてないと消去できないので、強制オプション-Dを使いましょう。
```shell
auth-app % git checkout main
Switched to branch 'main'

auth-app % git branch -D develop                 
Deleted branch develop (was 12e4bc1).
```

もし繋げる場合はこうです。
```shell
git branch --set-upstream-to=origin/develop develop
```

## リモートの変更を反映
**上司：「リモートの最新を忘れるなよ！」**
ひえ〜ストレスが溜まっていますね〜

fetch→mergeでもpullでも大丈夫そうですね。

軽くおさらいしておきましょう。

<dl>
<dt>git fetch</dt>
<dd>リモートの更新を取得するが、ローカルには反映しない。</dd>

<dt>git merge</dt>
<dd>ローカルに取り込んだリモートの変更を手動で反映する。</dd>

<dt>git pull</dt>
<dd>fetchとmergeを一度に行い、ローカルをリモートと同期。<dd/>

</dl>

```shell
auth-app % git fetch origin
auth-app % git merge develop                       
Already up to date.

# pullをすることで一気にもできる。
auth-app % git pull origin develop
```
コンフリクトが起きた時の対処法は下に載せておきます。

`git status`でどこがコンフリクト起きているかを確認できます。

## 間違えたときの対処法
よくありそうな2例を紹介していきます。

### pushの取り消し
上司から指示とは別に、間違えてdevelopブランチにpushしてしまいました。すると、奥から大きな声が。

**「何やってんのぉ！！！どこプッシュしてんだよ！！」**

どうすればいいのでしょうか。

![スクリーンショット 2024-12-12 21.27.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3738967/851f2f92-9e50-45c7-6f53-757bb27a5f9c.png)

ここにあるcommitコードをコピーして、`revert`することで、リモートにミスをした履歴を残したまま、元に戻すことができます（残しておいた方が、安全なのかな...）

`revert`は、`git reset`とは異なり、履歴を改変しないため、チーム開発でも安全に使用できます。

何もローカルを触っていない場合だと、vimで`:w`で保存して`:q`でファイル書き込み終了することでcommitをrevertできます。

履歴を完全に削除したい→ `git reset --hard` + `git push --force`
安全に取り消したい→ `git revert` + `git push`

```shell
auth-app % git revert 96fdd91b70a44be987dd3a962521cd6b342ffd93
[develop 6628feb] Revert "Revert "変更しました""
 1 file changed, 1 deletion(-)
```
しかし、すでにローカルも変更してしまっている場合は、以下のような警告も一緒にやってきます。


```shell
auth-app % git revert 652d1b333a2f6088d8e06883e536104e7c27e9ad
error: Your local changes to the following files would be overwritten by merge:
        nyaaaa.html
Please commit your changes or stash them before you merge.
Aborting
fatal: revert failed
```

この場合、現在の変更をローカルでコミットするか、stashで一時的に保存する方法があります。

今回は現在の変更をローカルでコミットする方法で行っていきます。
```shell
# 一旦ローカル内容を"変更1"でcommitする
# ローカルでのcommit履歴を確認
git log -2
commit f10bc268cd1e98baeb810503d0ab6c6e8c9639df (HEAD -> develop)
Author: shimayuu <@~jp>
Date:   Thu Dec 12 21:37:15 2024 +0900

    変更１

commit 652d1b333a2f6088d8e06883e536104e7c27e9ad (origin/develop)
Author: shimayuu <@~jp>
Date:   Thu Dec 12 21:26:47 2024 +0900

    変更しました
```
これでローカル内でcommitできているとわかったので、あとはrevertするだけです。
ちなみに、通常、HEAD は現在作業しているブランチ（今回の場合は develop）を指します。


ただ、現在のローカルを間違えて更新してしまっている場合は、コンフリクトが起こります。
```shell
git revert 652d1b333a2f6088d8e06883e536104e7c27e9ad
CONFLICT (modify/delete): nyaaaa.html deleted in parent of 652d1b3 (変更しました) and modified in HEAD.  Version HEAD of nyaaaa.html left in tree.
error: could not revert 652d1b3... 変更しました
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git revert --continue".
hint: You can instead skip this commit with "git revert --skip".
hint: To abort and get back to the state before "git revert",
hint: run "git revert --abort".
```
おっと、ここでconflitが起きてしまっています。

git statusで確認後、
```shell
auth-app % git status

On branch develop
Your branch is ahead of 'origin/develop' by 1 commit.
  (use "git push" to publish your local commits)

You are currently reverting commit 652d1b3.
  (fix conflicts and run "git revert --continue")
  (use "git revert --skip" to skip this patch)
  (use "git revert --abort" to cancel the revert operation)

Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add/rm <file>..." as appropriate to mark resolution)
        deleted by them: nyaaaa.html

no changes added to commit (use "git add" and/or "git commit -a")
```
変更場所を直すとうまくできました。

また、`git revert abc123 def456`のように複数のコミットを消すこともできます。

他にも、`stash`を利用してを利用して一時保存するには以下の通りです。

```shell
git stash
git revert 652d1b333a2f6088d8e06883e536104e7c27e9ad
git stash pop
```

### commit自体を消したい
もしcommit自体を消したい場合は、`reset`を使いましょう。また、 HEAD~`数字`とすることで、直前のコミットなどを指定できます。


<dl>
<dt>git reset --soft</dt>
<dd>インデックス: ステージング済みの変更はそのまま

ワークツリー: 変更内容はそのまま

<dd/>

<dt>git reset --mixed</dt>
<dd>
インデックス: ステージング解除される

ワークツリー: 変更内容はそのまま
<dd/>

<dt>git reset --hard</dt>
<dd>インデックス: リセットされる（変更が削除される）

ワークツリー: 作業ディレクトリもリセットされ、すべての変更が失われる<dd/>
</dl>

`git reset`は ローカルの変更のみ に影響し、リモートには影響を与えません。リモートの履歴も変更するには、次のように 強制プッシュ `git push --force` が必要です。

`git push --force-with-lease`にした場合、他の人が変更を追加していた場合は、エラーを出してプッシュを拒否します。


### 数個前のcommitに戻る。
`git checkout <commit-hash>`または、`<tag-name>`を指定することで移動することができます。
```shell
git checkout 6b0c192
```

ここでブランチを確認してみましょう。

```shell
auth-app % git branch
* (HEAD detached at 6b0c192)
  develop
  main
```

**detached HEADの状態とは...**

通常、GitではHEADは現在チェックアウトされているブランチを指しています。しかし、特定のコミット（ブランチではなく）をチェックアウトした場合、HEADはそのコミットを直接指すようになります。

この状態では、新しいコミットを作成しても、現在のブランチに反映されません。

新しいコミットを作成した場合、ブランチを作成しない限り、そのコミットは履歴から切り離され、失われる可能性があります。

なので、ブランチを新規作成していきましょう。

```shell
auth-app % git checkout -b feature-branch 6b0c192

Switched to a new branch 'feature-branch'

# ブランチを表示
shimamurayuudai@shimamurayuudainoMacBook-Air auth-app % git branch
  develop
* feature-branch
  main
```



## 載せ切らなかったもので、使いそうなものも随時載せておきます。
### rebase
リベースは、Gitの操作の一つで、あるブランチの変更を他のブランチの先頭に「積み上げる」操作です。
具体的には、あるブランチにコミットされた変更を、別のブランチ（通常はmainやmaster）の最新の変更の上に再適用することで、履歴をクリーンに保ち、後から変更を統合しやすくします。

```shell:リベース前のブランチ
main:    A --- B --- C
                 \
feature:          D --- E --- F

```


```shell
# your branch is main
git checkout feature
# your branch is feature
git rebase main
```

```shell:リベース後のブランチ
main:    A --- B --- C
                      \
feature:               D' --- E' --- F'
```
リベースにより、featureブランチの履歴がmainの最新のコミットCの後に「積み上げられ」ます。

コミットD、E、Fは新しいコミットとして適用されるため、異なるハッシュIDを持ちます。

リベースを行うと、ブランチの履歴が変更され、コミットが再配置されます。

履歴が直線的になり、見通しが良くなりますが、履歴が書き換えられるため、公開ブランチへのリベースは注意が必要そうですね。

その他にも、順番を変えたりと、かなり便利なものとなっているそうなので、また機会があればちゃんと勉強しようかと思います。

### remote

ローカルとリモートが別から作った場合などで、繋げる場合は、remoteを使います。

```shell
# 現在の繋がっているリモートリポジトリを確認
auth-app % git remote -v
origin  https://github.com/username/repository.git (fetch)
origin  https://github.com/username/repository.git (push)

# 新しく追加
auth-app % git remote add origin https://github.com/username/repository.git

# 更新させる
auth-app % git remote set-url origin https://github.com/username/new-repository.git
```

一応おさらいしておきます。

### pullコマンドの指定

<dl>
<dt>デフォルト挙動（merge）</dt>
<dd>

```shell
# 実行前
* (main)    A---B            ローカルのメインブランチ
                       \
* (origin/main)        C   リモートブランチの進捗

git pull

# 実行後
* (main)    A---B---M        マージコミット `M`
                       \   /
                        C   リモートブランチが統合される
```

</dd>

<dt>--rebase</dt>
<dd>

```shell
# 実行前
* (main)    A---B            ローカルのメインブランチ
                       \
* (origin/main)        C   リモートブランチの進捗

git pull --rebase

# 実行後
* (main)    A---C---B'       ローカルの変更 `B` がリモートの進捗 `C` の後ろに再適用される
```

</dd>

<dt>--ff-only</dt>
<dd>

```shell
# Fast-Forward可能な場合
# 実行前
* (main)    A---B            ローカルのメインブランチ
                       \
* (origin/main)        C   リモートブランチの進捗

git pull --ff-only

# 実行後
* (main)    A---B---C        ローカルブランチがリモートの進捗にFast-Forwardされる
```

```shell
# Fast-Forward不可能な場合
# 実行前
* (main)    A---B---D        ローカルが進んでいる
                       \
* (origin/main)        C   リモートブランチの進捗

git pull --ff-only

# 実行後（エラー）
fatal: Not possible to fast-forward, aborting.
```

</dd>

<dt>コンフリクトが発生した場合</dt>
<dd>

```shell
# 実行前
* (main)    A---B            ローカルのメインブランチ
                       \
* (origin/main)        C   リモートブランチの進捗

git pull

# コンフリクト発生時
Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.

# コンフリクト解消後
# 解消してから次を実行

git add file.txt
git merge --continue

# 実行後
* (main)    A---B---M        マージコミット `M`
                       \   /
                        C   コンフリクト解消後の状態
```

</dd>

<dt>デフォルト挙動（rebase）</dt>
<dd>

```shell
# 実行前
* (main)    A---B            ローカルのメインブランチ
                       \
* (origin/main)        C   リモートブランチの進捗

git pull --rebase

# コンフリクト発生時
CONFLICT (content): Merge conflict in file.txt
Resolve all conflicts manually, mark them as resolved with
"git add/rm <file>" and then run "git rebase --continue".

# コンフリクト解消後
# 解消してから次を実行

git add file.txt
git rebase --continue

# 実行後
* (main)    A---C---B'       ローカルの変更 `B` がリモートの進捗 `C` の後ろに再適用される
```

</dd>
</dl>


## mergeしたときのブランチの変化
<dl>
<dt>デフォルト挙動</dt>
<dd>

```shell
# 実行前
* (main)    A---B            ローカルのメインブランチ
                       \
* (feature)             C   マージしたいブランチ

git merge feature

# 実行後
* (main)    A---B---C        `feature` の変更がそのまま `main` に連結
* (feature)             C    （履歴は直線的）
```

</dd>

<dt>--no-ff</dt>
<dd>

```shell
# 実行前
* (main)    A---B            ローカルのメインブランチ
                       \
* (feature)             C   マージしたいブランチ

git merge --no-ff feature

# 実行後
* (main)    A---B---M        `M`: マージコミット
                       \   /
                        C   マージされた `feature`
```

</dd>

<dt>--ff-only</dt>
<dd>
リモートの進捗をローカルに取り込む際に、ファストフォワード（FF） のみを許可します。
ローカルに進捗がある場合や、FFができない場合はエラーになります

```shell
# Fast-Forward可能な場合
# 実行前
* (main)    A---B            ローカルのメインブランチ
                       \
* (feature)             C   マージしたいブランチ

git merge --ff-only feature

# 実行後
* (main)    A---B---C        履歴が直線的に更新
```

```shell
# Fast-Forward不可能な場合
# 実行前
* (main)    A---B---D        ローカルが進んでいる
                       \
* (feature)             C   マージしたいブランチ

git merge --ff-only feature

# 実行後（エラー）
fatal: Not possible to fast-forward, aborting.
```

</dd>

<dt>--squash</dt>
<dd>

```shell
# 実行前
* (main)    A---B            ローカルのメインブランチ
                       \
* (feature)             C   マージしたいブランチ

git merge --squash feature

# 実行後
* (main)    A---B---D'       `D'`: Squashされたコミット
* (feature)             C    （`feature` の変更はそのまま）
```

</dd>

<dt>コンフリクトが発生した場合</dt>
<dd>

```shell
# 実行前
* (main)    A---B---D        ローカルの進捗
                       \
* (feature)             C   コンフリクトの原因となるブランチ

git merge feature

# コンフリクト発生時
Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.

# コンフリクト解消後
# 解消してから次を実行

git add file.txt
git merge --continue

# 実行後
* (main)    A---B---D---M    マージコミット `M`
                       \   /
                        C   コンフリクト解消後の状態
```

</dd>
</dl>