【Git初心者へ送る記事①】"git push --force-with-lease"の素晴らしさ

本記事はQmonus Value Streamの投稿キャンペーン記事です。
# はじめに

「**これを知っているのと知らないのではチーム開発でのストレスが減る。**」
エンジニアやってる方であればこういうコマンドがいくつかあると思うのですが、**特にチーム開発に入りたての時**に知っておけばよかったと思うコマンドが、この`git push --force-with-lease`です。

未経験者の方はチーム開発のイメージがあまりわかないし、個人でGitHubを使用していると思うのであまり使うことがないと思いますが、これから実務でチーム開発を実施していく上では必ず知っておいた方がいいと思いますので、是非最後まで読んでみてください。

冒頭にも記載しましたが、これを知っておけば**かなりチーム開発でのストレスが減る**はずです:sunny:


さあ、それでは`git push --force-with-lease` の素晴らしさを解説していきます。

※細かな違いを上げていくと長くなるので、初心者の方が理解しやすいようにわかりやすい違いを記載します。

**すでに知ってるよ！という方はこちらをどうぞ**:writing_hand:

https://qiita.com/Dragon1208/items/dce382733a6b52087d02

https://qiita.com/Dragon1208/items/6e4b8d2cde763782c5d4
# 1. 通常の`push`, `push --force`との違いを理解する
まず、`git push --force-with-lease` を理解するために、通常の `git push` と `git push --force` について簡単に説明します。

### 1. 通常の `git push`:
   - 安全ですが、**リモートブランチに新しいcommitがある場合はpushが拒否されます。**
   例えば、他の人が同じdevelopブランチに変更をpushしていた場合、pushは失敗します。
   - **ファストフォワード**という特性があり、 ローカルブランチがリモートブランチの先頭から進んでいる場合にのみ成功します。リモートに新しいcommitがあるとプッシュが拒否されます。

### 2. `git push --force`:
   - いわゆるな力技です。リモートにローカルの状態を強制的に上書きします。
   - 他の人の変更を誤って消してしまう可能性があるので、使用するときはめちゃくちゃ注意しましょう。


そして、ここで `git push --force-with-lease` の出番です。
### 3. `git push --force-with-lease` 

   - リモートブランチが**最後に取得した時と同じ状態であることを確認してから**pushします。つまリ、**モートリポジトリの変更がローカルで把握している状態と一致している場合のみ** 強制的にプッシュを行うコマンドです。リモートリポジトリに他の変更がある場合はプッシュが拒否されます。
   ※ここだけ聞くと「通常のpushと同じじゃん」と思うかもしれませんが、大きな違いがありますので後程説明します。

   - 予期せぬ変更がリモートにある場合にpushを失敗させるので、他の人が加えた変更を誤って上書きしてしまう可能性がなくなります。


簡単な例え話で説明すると

- 通常の `push` は、「ドアが開いていれば入る、閉まっていれば諦める」という感じです。
- `--force` は、「ドアを壊してでも入る」というような乱暴な方法です。
- `--force-with-lease` は、「ドアが開いているか確認し、開いていれば入るが、誰かが中にいたら入らない」という方法でしょうか。

## 【ここでおさらい】:writing_hand: Fast Fowardとは:writing_hand:
```mermaid
gitGraph
    commit
    branch feature
    commit
    commit
    checkout main
    merge feature
    commit
    checkout feature
    commit
    checkout main
    merge feature
```

上の図で最初のmergeはメインに新しいcommitがないため**Fast-forward**です。
2回目のmergeはメインに新しいcommitがあるため**Non-fast-forward**です。

チーム開発では他の人がpushした新しいcommitにより、Non-fast-forwardになることがよくあります。

**「誰かのcommitが入った時点でFast-forwardじゃなくなる」** と覚えておきましょう。



# 2. 通常pushとの監視方法の違い
「他の人が新しい変更をpushしていた場合はpushは失敗します」と先に記載しましたが、ここでの通常pushとforce-with-leaseでは明確な違いがあるので解説していきます。

## 主な違い

### 1. 拒否の対象となる変更の範囲
   - 通常の `push`は単純にリモートに新しいcommitがあるかどうかをチェックします。
   - `--force-with-lease`は最後にfetchした時点からのリモートの変更をチェックします。

### 2. 失敗時の状況:
   - 通常の `push`: 新しいcommitがあれば常に失敗します。
   - `--force-with-lease`: リモートの状態が予期したものと異なる場合のみ失敗します。


### 例えば

`git rebase` でローカルのcommit履歴を整理した場合
   - 通常の `push` ではpushできません。
   - `--force-with-lease` では、リモートに予期せぬ変更がなければpushできます。
  
</br>

# 図で違いを理解しよう
```mermaid
sequenceDiagram
    participant L as Local
    participant R as Remote
    Note over L,R: 開発スタート
    R->>L: Fetch/Clone
    Note over L,R: リモートと同じ状態
    L->>L: あなたの変更(pushはまだ)
    R->>R: 他のメンバーのpushがあった
    Note over L,R: ここで差分ができる
    L->>R: 通常 Push (失敗する)
    L->>R: force-with-lease Push (失敗する)
    Note over L,R: この時点でLocalはまだupdateされていないので
    R->>L: Fetchしてローカルをupdate(mergeはしない)
    Note over L: Localは更新され<br>working treeはそのままの状態
    L->>R: 通常Push (失敗する)
    L->>R: Force-with-lease Push (成功する)
```

最後の部分が最大の肝です。
**通常の`push`はfetchしてもリモートに新しいcommitがあるためやはり失敗します。**
**`--force-with-lease`は最後のfetch以降にリモートが変更されていないことを確認してpushが成功します。**

理由はGitがデフォルトで「Fast Foward」マージしか許可しないためです。この部分の解説は省きますので、より深く知りたい方は是非調べてみてください

### 【ここでおさらい②】 :writing_hand:fetchとpullの違い(初心者用):writing_hand:
知ってる方はとばしてください

`git fetch` と `git pull` はリモートリポジトリからデータを取得するためのコマンドですが、それぞれの動作には重要な違いがあります。

#### `git fetch`
- **説明**: `git fetch` は、リモートリポジトリの最新の状態をローカルリポジトリに取得します。ただし、取得した内容をローカルのブランチにマージしません。取得したデータは、ローカルのリモート追跡ブランチ（例：`origin/main`）に保存されます。
- **主な特徴**:
  - リモートの変更を取得するだけで、ワーキングディレクトリや現在のブランチには影響を与えない。
  - ローカルブランチに変更をマージする前に、リモートの変更を確認できる。
  - 典型的な使用シナリオ: リモートの状態を確認し、マージやリベースを手動で行いたい場合。
#### `git pull`
- **説明**: `git pull` は、`git fetch` を実行してリモートリポジトリの最新の状態を取得し、その後に現在のブランチに取得した変更をマージします（またはリベースします）。
- **主な特徴**:
  - `fetch` と `merge` を連続して実行するコマンド。
  - ワーキングディレクトリや現在のブランチに直接影響を与えるため、リモートの変更を即座に取り込みたい場合に便利。
  - マージを行うため、コンフリクトが発生する可能性がある。
  - 典型的な使用シナリオ: ローカルブランチにリモートの変更をすぐに反映させたい場合。

#### まとめ
- **`git fetch`**: リモートの最新の状態をローカルに取得するが、ローカルブランチにマージしない。安全にリモートの変更を確認できる。
- **`git pull`**: リモートの最新の状態をローカルに取得し、現在のブランチにマージ（またはリベース）する。リモートの変更を即座に反映できるが、コンフリクトが発生する可能性がある。
い。

# 3. 注意点
**mainではなくdevelopブランチで実施しましょう。**
        いくら通常のpushより利点が多いといえども、チーム開発を行っていく上でいきなりmainに反映させるのはリスクがあります。大体はdevelopブランチで開発していくと思いますので（下図参照）、mainではなくdevelopにpushするように心がけてください。

![gitflow2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492127/215c003d-ea17-8293-50dd-dc6434dc21fb.png)

# おわりに
チーム開発に入る前に知っておきたかったという`git push --force-with-lease`コマンドを紹介しました。
未経験の方は友人を募ってチーム開発をして、いろいろなコマンドの使い方を実際に体感することが一番いいと思います。使ってみてコンフリクト起こしたり、解決してrebaseしたりして実際のコマンドの利便性がより理解できると思いますので、是非試してみてください。

## 続編
こちらは「やってしまった！」という状況を乗り切るためのコマンド集です。
使用頻度が高いものばかりなので、是非、チェックしてみてください:writing_hand:

https://qiita.com/Dragon1208/items/dce382733a6b52087d02