あんまり知られていないけど、地味に便利なgitコマンド


## はじめに

web系の企業でエンジニアをしながら個人的にもサービスを開発している橋田至といいます。

https://twitter.com/dall_develop

皆さんはgitコマンド使ってますか？
私は業務で使用するまではgitの意味がよく分かっておらず、add, commit, pushまで一気にやってくれたら便利なのになとか思ってました。

しかし、チーム開発では効率的に開発を進めるためにgitは非常に重要です。
GUIで便利にgitを操作できるツールもありますが、コマンド操作であれば共有が楽なためエンジニアとして会社で働きたい、共同開発したいと思っている人にはgitコマンドを覚えておくことは非常に重要です。

私自身未経験からエンジニアとして働き始めて、最初の壁がgitでした。
ここができないと開発する環境を整えるところで躓く上、みんなに迷惑がかかってしまいます。

実際私は働き始めて一週間後にアサインされた現場で`git push origin main --force`を実行してしまい、大変なことになりかけました😭

他の方がローカルにmainブランチを落としていたので、大事には至らなかったのですが、よく言われる一番やばいことをやらかしてしまいました。


そこから色々自分で学んで、今はかなりgitを使えるようになりました！

まだまだ知識不足ですが、自分が学んだ中で「これ地味に知られてないけど便利だな」と思ったコマンドがあります。

今回は私自身がよく業務で使用するけど、あまり知られていなさそうな便利コマンドを紹介します。

## git add -p
対話的にファイルの中の必要なコードだけステージングができます！
これができると例えばconsole.log()までステージングしてしまうことを防げます。

以下は、ファイルの変更内容としてconsole.logの行が追加された場合の例です。

```javascript:コード変更前
function add(a, b) {
    return a + b;
}
```


   ```javascript:コード変更後
   function add(a, b) {
       console.log("Adding two numbers");
       return a + b;
   }
   ```

この変更をコミットしたくない場合、`git add -p`を使用します。

```bash
$ git add -p
```

このコマンドを実行すると、次のようなプロンプトが表示されます：

```terminal:diff
diff --git a/example.js b/example.js
index xxxxxxx..yyyyyyy 100644
--- a/example.js
+++ b/example.js
@@ -1,4 +1,5 @@
 function add(a, b) {
+    console.log("Adding two numbers");
     return a + b;
 }

Stage this hunk [y,n,q,a,d,/,e,?]? 
```

ここで、以下のオプションが提供されます：

- y - このハンクをステージする
- n - このハンクをステージしない
- q - パッチの追加を終了する
- a - このハンクと以降の全てのハンクをステージする
- d - このハンクと以降のハンクをステージしない
- / - 正規表現にマッチする次のハンクを検索する
- e - 現在のハンクを直接編集する
- ? - これらのオプションのヘルプを表示する

`console.log`の変更をステージングしない場合は、`n`を選択します。そして、残りのハンクについての選択肢がある場合は、それを繰り返します。

あとはsを使用すると、単位を分割できます。
よく使うのはy,n,s,qでしょうか

---

この方法を使用すると、`console.log`のようなデバッグ用のコードや、まだコミットしたくない変更を含むハンクを簡単にスキップできます。


## git diff --name-only <commit> <commit>

`git diff`コマンドは、Gitのコミット間の差分を確認する際に非常に有用です。
特に、`--name-only`オプションを使用すると、変更されたファイルの名前だけを表示することができます。

これは、大きな変更があった場合や、特定のファイルが変更されたかどうかを迅速に調査したい場合に役立ちます。

#### 使用方法

基本的な使用方法は以下の通りです。

```
git diff --name-only <commit1> <commit2>
```

ここで、`<commit1>`と`<commit2>`は比較したい2つのコミットのハッシュまたは参照名（例: `HEAD`, `master`, `origin/main`など）です。

#### 実例

例として、以下の2つのコミットの間の変更を確認します。

- コミットA: `abcd1234`
- コミットB: `efgh5678`

実行コマンドは以下のようになります。

```
git diff --name-only abcd1234 efgh5678
```

結果、以下のように変更されたファイルの名前だけが表示されるとします。

```
README.md
src/main.c
src/functions.c
```

これにより、これらの3つのファイルが2つのコミットの間で変更されたことがわかります。

---

`git diff --name-only`は、差分を見るだけでなく、スクリプトやツールの中で変更されたファイルのリストを取得する場合など、様々なシチュエーションで非常に便利です。


## git pull --rebase

`git pull`は通常、リモートリポジトリの変更をローカルに取り込む際に使用します。しかし、単純な`git pull`はマージを行い、それが原因で不要なマージコミットが発生する場合があります。

ここで`--rebase`オプションを使用すると、ローカルの変更を一旦「取り置き」してから、リモートの変更を取り込み、その後「取り置き」していたローカルの変更を再適用します。この結果、直線的な履歴が維持されます。

#### 例: git pull --rebase の使用

1. 以下の状況を考えます。

    * リモートリポジトリの履歴: `A -> B`
    * あなたのローカルリポジトリの履歴: `A -> C`

2. `git pull`を実行すると、以下のような履歴が生成されます。

    ```
    A -> B -> Merge commit (B and C)
           /          \
         A            C
    ```

3. 一方、`git pull --rebase`を実行すると、以下のような履歴が生成されます。

    ```
    A -> B -> C (rebased)
    ```

    リベースにより、コミットCがコミットBの後に再適用されます。

#### 注意点

`git pull --rebase`を使用する際には以下の注意点があります。

* コンフリクトが発生する可能性があります。この場合、コンフリクトを解消した後に`git rebase --continue`を実行してリベースを続行する必要があります。
* 共同で作業している他の開発者に影響を与える可能性があるため、公開されているブランチでのリベースは推奨されません。

---

`git pull --rebase`は、特定の状況下で役立つコマンドですが、使用する際には上記の注意点を考慮してください。

またgit pullはgit fetchとgit mergeを組み合わせたコマンドになります。
どう使うかは議論が分かれるところだとは思いますが、私は最初のうちはgit pullではなく、git fetch & git rebaseを使用するようにと教わりました。

rebaseを使うかmergeを使うかも会社やプロジェクトによって変わるかとは思いますが、初学者のうちは一つ一つの段階を理解するために分けた方が良いかなと思います。


## git merge --squash

`git merge --squash` は特定のブランチの変更を一つのコミットとして統合するためのオプションです。これは、たくさんのコミットを持つトピックブランチや機能ブランチを主ブランチに統合する際に、その全てのコミット履歴を持ち込むのではなく、単一のコミットとして取り込みたいときに非常に便利です。

#### 使用方法

まず、基本的なコマンドの形は以下の通りです。

```bash
git merge --squash [ブランチ名]
```

#### 実例

例として、`feature-branch` というブランチがあったとします。このブランチには10のコミットがあり、これを `main` ブランチにマージしたいとしますが、単一のコミットとして取り込みたいとします。

以下のステップで実行します：

1. まず、`main` ブランチに移動します。

    ```bash
    git checkout main
    ```

2. 次に、`git merge --squash` を使用して `feature-branch` の変更をスカッシュマージします。

    ```bash
    git merge --squash feature-branch
    ```

3. この時点で、変更はステージングエリアに追加されますが、まだコミットされていません。このため、コミットを行う必要があります。

    ```bash
    git commit -m "Squashed commit of feature-branch changes"
    ```

これで、`main` ブランチに `feature-branch` の全ての変更が1つのコミットとして統合されました。

これによって、mainブランチに大量のコミットログが表示されて、ログが汚染されるということがなくなります。

#### 注意点

- `git merge --squash` は元のブランチのコミット履歴を失わない。つまり、`feature-branch` のコミット履歴はそのまま保持される。
- スカッシュマージは元のブランチのコミット履歴を「フラット化」するので、そのブランチの変更履歴の詳細を追いたい場合は適していない。

#### 要約

`git merge --squash` は特定のブランチの変更を単一のコミットとして統合したい時に非常に役立つコマンドです。しかし、変更の詳細な履歴が必要な場合は使用しない方が良いでしょう。

## git stash

#### 一時ファイルstashの使い方

git stashは、現在の作業ブランチ上で行われた変更を一時的に保存し、クリーンな状態に戻すための便利なコマンドです。特に、現在のブランチに関係なく別の作業を行いたい場合や、他のブランチに切り替えたいときなどに使用します。

例えばcheckoutしたいときやrebaseしたい時に使用します。

#### 基本的な使い方
##### 1. 一時保存する

現在の変更を一時保存するには、以下のコマンドを使います。

```bash
git stash
```

`git stash -u` であればunstagedな追跡されていないファイルも保存可能です

stashにメッセージを追加したい場合は

```bash
git stash push "message"
```

になります。

※git stash saveは非推奨です

`git stash show` を使用すると、stash の要約を表示できます。

##### 2. git stashのリストを表示

```bash
git stash list
```

##### 3. stashの削除

```jsx
git stash drop
```

stashを全部消したい場合

```
git stash clear
```

##### 4. 一時保存した変更を適用する

```bash
git stash pop
```

ちなみにgit stash popではstashリストからその変更を削除できますが、`git stash apply`ならstashが残ります。

##### 5. 特定のファイルだけをスタッシュする

```bash
git stash push -m "メッセージ" ファイル名
```

## git rebase --autostash

rebaseするときは

```bash
git stash
git rebase
git stash pop
```

の3工程が必要になります

git rebase --autostashを使うとこの3工程が一発で行えます


## git push origin ブランチ名  --force-with-lease

--force-with-lease オプションを使用すると、安全な方法でリモートブランチを上書きすることができます。

`git push --force-with-lease` は、通常の `--force` オプションのより安全な代替手段です。
`--force` はリモートの変更を無視してローカルの変更を強制的にプッシュしますが、`--force-with-lease` はリモートに未知の変更がある場合、プッシュを拒否します。

git rebaseした場合、リモートのブランチに変更をpushしたい場合、force pushじゃないとpushできません。
その時にpush -fは怖いなという時にこれを使ってます。

#### 使用例

リモートの `feature-branch` に変更をプッシュしようとして、何らかの理由で強制プッシュが必要だった場合：

```
git push origin feature-branch --force-with-lease
```

#### どのように動作するか？

`--force-with-lease` は以下のように動作します：

1. ローカルの最後の知っているリモートの状態と、実際のリモートの状態を比較します。
2. これらが同じである場合、プッシュが実行されます。
3. それらが異なる場合、他の誰かが間の変更をプッシュした可能性があるため、プッシュは拒否されます。

この方法では、他の開発者が行った変更を誤って上書きするリスクを軽減できます。

#### 要約

`git push --force-with-lease` は、強制プッシュを行う際の安全な手段を提供します。これを使用することで、他の開発者の変更を上書きするリスクを避けることができます。通常の `--force` オプションを使用する前に、このオプションを検討することを強くおすすめします。


## git diff HEAD^ HEAD

`git diff` コマンドは、git における差分を表示するための非常に有用なツールです。
特に `HEAD^` と `HEAD` という2つのリファレンスを用いると、直前のコミットと最新のコミットの差分を確認できます。

#### 概念の解説

- **`HEAD`**: 現在のブランチの最新のコミットを指します。
- **`HEAD^`**: `HEAD` の1つ前のコミット、つまり直前のコミットを指します。

#### 使い方

以下のようなコマンドで実行できます。

```bash
$ git diff HEAD^ HEAD
```

#### 例

以下のシナリオを考えます：

1. ファイル `example.txt` を作成し、初めに "first line" と書き込む。
2. その変更をコミットする。
3. `example.txt` に "second line" を追加する。
4. その変更もコミットする。

この時点で、`git diff HEAD^ HEAD` を実行すると、次のような出力が得られるはずです。

```diff
diff --git a/example.txt b/example.txt
index xxxxxxx..yyyyyyy 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 first line
+second line
```

この出力から、"second line" が追加されたことが分かります。

#### 要約

`git diff HEAD^ HEAD` は、最新のコミットとその1つ前のコミットの差分を比較・確認するのに便利なコマンドです。このコマンドを使用することで、最近の変更点を素早く確認することができます。

ただ最近vscodeの拡張機能でgitLensを入れてみたら簡単に差分を表示できたので、そっちの方が人によっては便利かも知れません。

差分の確認はCLIよりGUIの方が使いやすいですね。


## git rebase -i HEAD~[対象のコミット数]

こちらはrebaseしてmainのコミットもPRに含まれてしまった場合に使用しています。
自分のrebaseの仕方がおかしくてこれを使わざるを得なくなってるかも知れません。

もし他に解決策など知っている方いたら教えていただけると助かります😭

このコマンドは、直近のコミットを対話的にリベースする際に使用します。

#### 概要

`git rebase -i`は、対話的（interactive）リベースの略で、コミットの履歴を編集するためのツールです。特定のコミットを変更、削除、結合する場合など、歴史を改変したいときに役立ちます。

#### 使用方法

以下は`git rebase -i HEAD~3`を実行した場合の例です。

```bash
$ git rebase -i HEAD~3
```

このコマンドを実行すると、直近の3つのコミットがテキストエディタに表示されます。

```
pick a123456 コミットメッセージ1
pick b789012 コミットメッセージ2
pick c345678 コミットメッセージ3

# Rebase d456789..c345678 onto d456789 (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
```

ここで、各コミットの前のキーワード（例：`pick`）を変更することで、そのコミットに対する操作を指定することができます。

vim操作

1. vimが開かれるので、iを押してinsertモードに
1. いらないコミットがpickになっているので、dropに書き換える
1. escを押してinsertモードを終了
1. :wqで変更を保存

例えば、`コミットメッセージ2`を変更したい場合、`pick`を`reword`に変更します。
`コミットメッセージ3`を表示したくない時は`pick`を`drop`に変更します。

```
pick a123456 コミットメッセージ1
reword b789012 コミットメッセージ2
drop c345678 コミットメッセージ3
```

このように変更して保存・終了すると、指定したコミットのメッセージを編集するプロンプトが表示されます。

#### 注意点

- リベース操作は、コミットの歴史を改変するものなので、既に公開しているブランチ（特に`main`や`master`などの主要ブランチ）での使用は避けるべきです。
- リベースの操作はやや高度な操作のため、失敗するとリポジトリの状態が複雑になる可能性があります。したがって、操作前には必ずバックアップを取るなどの対策を推奨します。


## ghコマンド

これはコマンドラインからgithubを操作できるようになるツールです。

https://cli.github.com/

PRの操作がかなり便利になります。これはかなり使えると思います。

```bash
# プルリクエストのブランチへのチェックアウト
$ gh pr checkout {<number> | <url> | <branch>} [flags]

# 現在のブランチからのプルリクエストの作成
$ gh pr create [flags]

# プルリクエストのクローズ
$ gh pr close {<number> | <url> | <branch>} [flags]

# プルリクエストの差分を確認
$ gh pr diff {<number> | <url>} [flags]

# プルリクエストの一覧を取得
$ gh pr list [flags]

# プルリクエストをマージ
$ gh pr merge [<number> | <url> | <branch>] [flags]

# プルリクエストにレビューを追加
$ gh pr review [<number> | <url> | <branch>] [flags]

# プルリクエストの内容を閲覧
$ gh pr view [<number> | <url> | <branch>] [flags]

# 自分に関係のあるプルリクエストのステータスを閲覧
$ gh pr status [flags]

# ドラフトのプルリクエストをレビューに変更する
$ gh pr ready [<number> | <url> | <branch>] [flags]

# クローズしたプルリクエストを再度オープンにする
$ gh pr reopen {<number> | <url> | <branch>} [flags]

```


## エイリアス
よく使うGitコマンドはshellにエイリアスを登録しておくのがオススメです

```bash
// エイリアスの例
switch = sw
branch = br
status = st
commit = co

// 設定用コマンド
git config --global alias.sw switch
git config --global alias.br branch
git config --global alias.st status
git config --global alias.co commit
```

## まとめ

今回知られていなさそうだけど、業務でよく使用するコマンドを紹介しました！
他にも`これ知られていないけど便利だよ！`みたいなコマンドがあれば、コメントで教えていただけると助かります！

X/Twitterで日々技術の発信をしているので、よかったら是非ともフォローをお願いします！
また、この記事が参考になったらいいねしてもらえると励みになります！

ありがとうございました！

https://twitter.com/dall_develop



## 参考

https://www.youtube.com/watch?v=1be137NUOa4

https://liginc.co.jp/387757

https://zenn.dev/fusic/articles/336c5192d2f162