---
title: "git rebaseとは？使い方とmergeとの違い・コンフリクト解決・やり直しを実行例で解説"
url: "https://www.issoh.co.jp/tech/details/8926/"
published: 2025-09-19
updated: 2026-10-31
categories: ["GitHub"]
publisher: "株式会社一創"
---

# git rebaseとは？使い方とmergeとの違い・コンフリクト解決・やり直しを実行例で解説

`git rebase`は、ブランチのコミットを別のコミットの上に載せ直して、履歴を一直線にするコマンドです。`git merge`が（fast-forwardできない場合に）マージコミットを作って分岐の経緯を残すのに対し、rebaseはコミットを作り直すためハッシュIDが変わります。この記事では、rebase前後の`git log`の実行結果を見せながら、基本の使い方、mergeとの使い分け、`-i`によるコミット整理、コンフリクト解決、rebase後のpush、`reflog`でのやり直しまでを順に説明します。実行例はApple Git 2.50.1で手元のリポジトリを使って取った出力で、オプションの仕様はGit 2.56.0のドキュメントで照合しています。

## まとめ：git rebaseの要点

- **仕組み**：分岐元を別のコミットへ移し、自分のコミットを1つずつ適用し直す。内容が同じでも**ハッシュIDが新しいコミットに置き換わる**。
- **mergeとの使い分け**：まだpushしていない自分のブランチを最新の`main`に追従させる・履歴を整えるならrebase。他人と共有したブランチの統合はmerge。
- **やめるべき場面**：他人がpullしたブランチ（`main`や共同作業中のブランチ）はrebaseしない。相手の履歴と食い違い、重複コミットが生まれる。
- **止まったら**：コンフリクトは解決して`git add`→`git rebase --continue`。やめるなら`git rebase --abort`で開始前に戻る。
- **pushは条件付きで**：rebase後の強制pushは`--force-with-lease --force-if-includes`。素の`--force`は他人の追加コミットを消す。
- **やり直し**：`git reflog`でrebase開始前のコミットを探して戻せる。`ORIG_HEAD`が開始前の先端を指していると確認できれば`git reset --hard ORIG_HEAD`が最短。

## git rebaseとは：コミットを新しい土台に載せ直す操作

rebaseは「base（土台）を付け替える」という名前のとおり、ブランチが分岐した基点を別のコミットに移す操作です。`feature`ブランチで`git rebase main`を実行すると、Gitは`feature`にだけあるコミットを取り出し、最新の`main`の先端から順番に適用し直します。

次は`main`に1コミット（B）、`feature`に2コミット（X・Y）がある状態でrebaseした実行結果です。

```
$ git log --graph --oneline --all
* baadb75 Y: feature2
* b613285 X: feature1
| * 714691d B: main側
|/
* a1c1d9b A: 初期

$ git rebase main
Successfully rebased and updated refs/heads/feature.

$ git log --graph --oneline --all
* ff333c1 Y: feature2
* 9e2c23d X: feature1
* 714691d B: main側
* a1c1d9b A: 初期
```

分岐が消えて一直線になりました。ここで見てほしいのはハッシュIDです。XとYは変更内容もメッセージも同じなのに、`b613285`→`9e2c23d`、`baadb75`→`ff333c1`に変わっています。Xは親がAからBに、Yは親が元のXから新しいXに変わったため、どちらも**別のコミットとして作り直された**からです。元のXとYはどのブランチからもたどれなくなり、一定期間を過ぎるとガベージコレクションで消えます。rebaseが「履歴の書き換え」と呼ばれ、共有ブランチで禁止される理由はここにあります。

## git rebaseとgit mergeの違いと使い分け

分岐した2つのブランチをmergeすると、統合のための**マージコミットが新しく作られ**、分岐と合流の経緯がそのまま残ります（片方が他方の祖先ならfast-forwardでブランチの位置が進むだけです）。既存のコミットには手を付けません。rebaseはマージコミットを作らず、自分のコミットを作り直して**一直線に並べます**。コンフリクトがない単純な例なら最終的なファイルの中身は同じになりますが、rebaseではコミットごとに解決や`--skip`をするため、結果が変わることがあります。rebase後は`git diff`やテストで中身を確かめてください。

| 観点        | git merge            | git rebase   |
| --------- | -------------------- | ------------ |
| 履歴の形      | 分岐と合流が残る             | 一直線になる       |
| マージコミット   | 作る（fast-forward時を除く） | 作らない         |
| 既存コミットのID | 変わらない                | 自分側のコミットが変わる |
| コンフリクトの解決 | まとめて1回               | コミットごとに発生しうる |
| 共有済みブランチ  | 安全                   | 使わない         |

### rebaseを使う場面

使う場面は、まだpushしていない（または自分しか使っていない）作業ブランチに限られます。典型は2つです。1つ目は、プルリクエストを出す前に作業ブランチを最新の`main`へ追従させること。レビューする側は`main`との差分だけを読めばよくなります。2つ目は、「typo修正」「レビュー対応」のような細かいコミットを`-i`でまとめて、意味のある単位に整えることです。

### rebaseをやめるべき場面

`main`・`develop`のように他人がpullしているブランチはrebaseしないでください。Pro Git（git-scm.comの公式書籍）も「公開リポジトリにプッシュしたコミットをリベースしてはいけない」を原則に挙げています。もう1つ避けたいのが、コミットが数十個あり同じファイルを何度も触っているブランチの追従です。rebaseはコンフリクトをコミット単位で解かせるので、同じ箇所を何度も直すことになります。この場合は`git merge main`で1回にまとめるほうが早く、解決ミスも減ります。マージコミットを含むブランチも、既定のrebaseではマージコミットが消えます（後述の`--rebase-merges`）。

## git rebaseの基本的な使い方

### 作業ブランチを最新のmainへ追従させる手順

基本の書式は`git rebase <upstream> [<branch>]`です。`<upstream>`が載せ直す先、`<branch>`が動かすブランチで、`<branch>`を省くと今いるブランチが対象になります。

```
git switch feature
git fetch origin
git rebase origin/main
```

リモートの最新を取り込むなら、ローカルの`main`ではなく`fetch`直後の`origin/main`を指定します。`<upstream>`自体を省略すると、ブランチに設定された追跡先（`branch.<name>.merge`）が使われます。

### `--onto`による載せ替え先と対象範囲の指定

分岐の一部だけを別の基点へ移すときは`--onto`を使います。`git rebase --onto <新しい基点> <ここより後を移す> <ブランチ>`という読み方です。

```
git rebase --onto main develop feature
```

これは「`develop`から分岐した`feature`のうち、`develop`にないコミットだけを`main`の上へ移す」指定です。`develop`の未リリースのコミットを持ち込まずに`feature`だけを先に出したいときに使います。親ブランチがスカッシュマージされた後の派生ブランチの付け替えも同じ形で、手順は[git squashとは？コミットが多すぎる履歴をまとめる手順とスカッシュマージの注意点](/tech/details/2506/)で実行例つきで扱っています。

### git pull `--rebase`とpull.rebaseの設定

`git pull`は`fetch`のあとに統合を行います。[git-pullのドキュメント](https://git-scm.com/docs/git-pull)（2.56.0）では既定は`--ff-only`で、手元とリモートが分岐していると失敗します。統合方法は`--rebase`（rebase）・`--no-rebase`（merge）などで選びます。mergeを選ぶと`Merge branch 'main' of ...`のようなマージコミットが増えますが、`git pull --rebase`なら手元の未pushコミットを取り込み後の先端に載せ直すので増えません。毎回付けるのが面倒なら設定で既定にします。

```
git config --global pull.rebase true
```

作業ツリーに未コミットの変更があるとrebaseは開始を拒否します。`git rebase --autostash`（または`rebase.autoStash`設定）を使うと、開始前に一時的にstashし、終了後に戻します。ドキュメントは、戻すときにコンフリクトが起きうる点に注意を促しています。

## インタラクティブリベース（-i）によるコミット整理

`git rebase -i`（interactive）は、対象のコミット列をtodoリストとしてエディタで開き、1行ずつ操作を指定して履歴を編集する機能です。直近3コミットなら`git rebase -i HEAD~3`、`main`から分岐した後の全コミットなら`git rebase -i main`と指定します。

### todoリストの読み方とコマンド

エディタには次のようなtodoリストが開きます（Git 2.50.1の実物。行頭のコマンドを書き換えて保存すると、上から順に実行されます）。

```
pick 9e2c23d # X: feature1
pick ff333c1 # Y: feature2
fixup ce5abcf # fixup! Y: feature2
pick a336e32 # Z: 修正
# Rebase 714691d..ce5abcf onto 714691d (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
...
# If you remove a line here THAT COMMIT WILL BE LOST.
```

| コマンド   | 動作                    |
| ------ | --------------------- |
| pick   | そのまま採用（行の並べ替えで順序も変わる） |
| reword | メッセージだけ書き直す           |
| edit   | そこで止まり、修正やコミット分割をする   |
| squash | 直前に統合し、両方のメッセージを編集    |
| fixup  | 直前に統合し、自分のメッセージは捨てる   |
| drop   | コミットを削除（行を消しても同じ）     |
| exec   | その位置でシェルコマンドを実行       |
| break  | その位置で一時停止             |

行を消すとそのコミットは失われる、という警告がリストの末尾に出ます。逆に全行を消して保存すると、rebaseは中止されて何も変わりません。編集を始めてから迷ったら、全行を消して閉じるのが安全な抜け方です。

### `--autosquash`とfixupコミット

上のtodoリストで、3行目の`fixup ce5abcf`が修正対象のYの直後に並んでいるのは`--autosquash`の効果です。修正をコミットするときに`git commit --fixup=<対象>`を使うと「fixup! 対象のメッセージ」という件名になり、`git rebase -i --autosquash`で対象の直後へ自動で移されて`fixup`が付きます。

```
git add f.txt                  # 修正内容をステージ
git commit --fixup=ff333c1
git rebase -i --autosquash main
```

squashとfixupの使い分け、コミットをまとめた後の扱いは[git squashの記事](/tech/details/2506/)で詳しく説明しています。

### execによるコミット単位のテスト実行

`-x`（`--exec`）を付けると、各コミットの適用後にコマンドを実行し、失敗したところでrebaseが止まります。整理した後の各コミットが単体でもビルド・テストを通るかを確かめたいときに使います。

```
git rebase -i --exec "npm test" main
```

途中のコミットでテストが壊れていると、`git bisect`で原因を探すときに誤判定が起きます。コミットを並べ替えた後に一度通しておけば、テストが失敗する中間コミットをその場で特定できます。CIで同じ検査を自動化する場合は、弊社の[DevOps・CI/CD導入支援](https://www.issoh.co.jp/service/system/devops/)でも相談を受けています。

## `--update-refs`による積み上げたブランチの一括更新

`part1`の上に`part2`を作るように、ブランチを積み上げて小さなプルリクエストに分けていると、`main`が進んだときに両方を付け替える必要があります。Git 2.38で追加された`--update-refs`を使うと、先端の`part2`をrebaseするだけで、途中を指している`part1`も新しいコミットへ動きます。

```
$ git switch part2
$ git rebase -i --update-refs main
pick e5c3ae3 # P1
update-ref refs/heads/part1
pick 91db6e7 # P2

Successfully rebased and updated refs/heads/part2.
Updated the following refs with --update-refs:
	refs/heads/part1
```

todoリストに`update-ref refs/heads/part1`の行が入り、rebaseの最後に`part1`が新しいP1を指すよう更新されます。実行後は`part1`が`part2`の祖先のまま保たれていました。`-i`なしの`git rebase --update-refs main`でも同じように動きます。毎回付けるなら`git config --global rebase.updateRefs true`で既定にできます。

ただし[git-rebaseのドキュメント](https://git-scm.com/docs/git-rebase)のとおり、**別のworktreeでチェックアウト中のブランチは更新されません**。`git worktree`で`part1`を開いたままだと、`part1`だけ古い位置に残ります。更新した各ブランチをすでにpushしていた場合は、それぞれ強制pushが必要です（未pushのブランチは通常のpushで足ります）。

## rebase中のコンフリクト解決（continue・skip・abort）

自分のコミットと載せ直す先が同じ箇所を変更していると、そのコミットの適用でrebaseが止まります。実行結果は次のとおりです。

```
$ git rebase main
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
error: could not apply fc4d624... C: aを書き換え
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
```

メッセージが示すとおり、選択肢は3つです。

```
git add a.txt            # 解決したファイルを登録
git rebase --continue    # 次のコミットへ進む
git rebase --skip        # このコミットを捨てて進む
git rebase --abort       # rebase開始前の状態に戻す
```

rebaseはコミットを1つずつ適用するので、コンフリクトもコミットの数だけ繰り返し起きる可能性があります。注意点として、rebase中の`ours`は載せ直す先（`main`に、そこまで適用済みの自分のコミットを加えた状態）、`theirs`はいま適用しようとしている自分のコミットを指し、mergeのときと逆になります。`git checkout --ours`で自分の変更を残すつもりが`main`側を採っていた、という取り違えが起きやすいところです。解決の手順そのものは[コンフリクトとは？Gitで変更が衝突する仕組みと解消手順・予防設計を実装視点で解説](/tech/details/13562/)にまとめています。

同じコンフリクトを何度も解かされるなら、解決内容を記録して次回から自動で当てる`rerere`を有効にします。

```
git config --global rerere.enabled true
```

## マージコミットを含むブランチと`--rebase-merges`によるマージの再作成

既定のrebaseは、対象範囲のマージコミットをtodoリストから落とし、一本の直線に並べ直します。トピックブランチ`t1`を`--no-ff`でマージ済みのブランチを`main`へrebaseした結果を比べると、違いがはっきり出ます。

```
$ git rebase main                   # 既定
$ git log --oneline --graph main..rm
* b356567 T1

$ git reset --hard ORIG_HEAD         # 開始前の状態に戻してから比べる
$ git rebase --rebase-merges main   # マージコミットを作り直す
$ git log --oneline --graph main..rm
* 6f70c18 Merge t1
* b356567 T1
```

既定では「Merge t1」が消えています。マージの単位で履歴を読みたいブランチでは`--rebase-merges`を付けてください。ドキュメントによれば、元のマージコミットで手作業で解決したコンフリクトや修正は、作り直すときに改めて解決が必要です。

## rebase後のpush：`--force-with-lease`と`--force-if-includes`

push済みのブランチをrebaseすると、リモートの履歴と食い違うので通常の`git push`は`! [rejected]`で拒否されます。通すには強制pushが要りますが、素の`--force`は自分がrebaseしている間に他人が追加したコミットまで消します。

そこで使うのが`--force-with-lease`です。「手元が最後に見たリモートの位置から動いていなければ上書きする」条件付きの強制pushで、他人のpushを見ていなければ拒否されます。

```
$ git push --force-with-lease origin feature
 ! [rejected]        feature -> feature (stale info)
```

ところが、**pushの前に`git fetch`すると、この保護は効かなくなります**。fetchで「最後に見た位置」が他人のコミットに更新されるため、条件を満たしてしまいます。手元のリポジトリで試すと、fetch後の`--force-with-lease`は`(forced update)`で通り、他人のコミットが消えました。Git 2.30で入った`--force-if-includes`を併用すると、リモートの先端を取り込まないまま上書きしようとしていることを検出して拒否します。

```
$ git fetch
$ git push --force-with-lease --force-if-includes origin feature
 ! [rejected]        feature -> feature (remote ref updated since checkout)
```

エディタやツールが裏で定期的にfetchしている環境では、`--force-with-lease`だけでは足りません。`git config --global push.useForceIfIncludes true`で既定にしておくのが確実です。

## 共有ブランチをrebaseされたときの対処

自分が作業の土台にしていたブランチを誰かがrebaseして強制pushした場合、mergeで統合すると（`git pull --no-rebase`など）、書き換え前と後の同じ内容のコミットが二重に履歴に残ります。[Pro Gitのリベースの章](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E6%A9%9F%E8%83%BD-%E3%83%AA%E3%83%99%E3%83%BC%E3%82%B9)は、この場合は`git pull --rebase`を使うよう勧めています。Gitはコミットの差分から計算した「パッチID」で同じ内容のコミットを見分け、相手側で作り直されたものを除いて、自分にしかないコミットだけを載せ直します。

```
git fetch
git rebase origin/feature   # または git pull --rebase
```

相手のrebaseで内容まで変わったコミットはパッチIDが一致しないため、自動では除かれません。その場合は`git rebase -i origin/feature`で、自分のものでない行を`drop`します。根本的な対策は、共有ブランチをrebaseしない運用を決めておくことです。

## GitHubの「Rebase and merge」とgit rebaseの違い

GitHubのプルリクエストには、マージ方式として「Create a merge commit」「Squash and merge」「Rebase and merge」の3つがあります。Rebase and mergeは、プルリクエストのコミットをマージコミットなしで1つずつベースブランチへ追加します。ただし[GitHubのドキュメント](https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/about-merge-methods-on-github)によると、手元の`git rebase`とは次の点が違います。

- **コミッター情報とSHAが常に変わる**：`git rebase`は祖先の上に載せ直すだけならコミッター情報を変えませんが、GitHubは毎回コミッター情報を更新し、新しいSHAのコミットを作ります。
- **最初から空のコミットは落とす**：`git commit --allow-empty`で作ったコミットは追加されません。手元の`git rebase`は既定で残します。
- **署名は検証済みにならない**：GitHubは作成者の秘密鍵を持たないため、追加されたコミットに署名できません。GitHubのドキュメントは回避策として手元でrebaseしてマージし、ベースブランチへpushする方法を挙げています。作り直したコミットは`git rebase -S`（または`commit.gpgSign`の設定）で署名し直さないと署名されない点に注意してください。

GitHubが自動でrebaseできないコンフリクトがあるときは、ボタンが使えません。手元で`git rebase`してコンフリクトを解き、強制pushしてから改めてマージします。3つの方式の選び方とリポジトリ側の設定は[GitHubのPull Requestの作り方：作成手順とマージ方式・レビュー必須化の決め方](/tech/details/18004/)で解説しています。

## ORIG\_HEADとreflogによるrebaseのやり直し

rebaseを完了した後で戻したくなっても、元のコミットはすぐには消えません。冒頭の例では、rebase後の`ORIG_HEAD`がrebase前の先端`baadb75`を指していました。ただし`ORIG_HEAD`は`reset`・`merge`などでも上書きされ、`edit`で止めたrebase中に`git reset`を使った場合も書き換わります。使う前に`git log -1 ORIG_HEAD`で中身を見て、下のreflogのrebase開始前の行と同じコミットか照合してください。

```
git log -1 --oneline ORIG_HEAD   # rebase前の先端か確認
git reset --hard ORIG_HEAD
```

一致しないときや他の操作をした後は、`git reflog`でrebase開始前の行を探します。

```
$ git reflog -n 6
ff333c1 HEAD@{0}: rebase (finish): returning to refs/heads/feature
ff333c1 HEAD@{1}: rebase (pick): Y: feature2
9e2c23d HEAD@{2}: rebase (pick): X: feature1
714691d HEAD@{3}: rebase (start): checkout main
baadb75 HEAD@{4}: commit: Y: feature2
b613285 HEAD@{5}: commit: X: feature1

$ git reset --hard HEAD@{4}
```

戻り先は`rebase (start)`の1つ下の行（ここでは`HEAD@{4}`）です。`reset --hard`は作業ツリーの未コミットの変更も消すので、実行前に`git status`で確認してください。

reflogにも期限があります。[git-configのgc設定](https://git-scm.com/docs/git-config)では、通常のエントリは`gc.reflogExpire`の既定90日、現在の先端からたどれないエントリは`gc.reflogExpireUnreachable`の既定30日で整理されます。rebaseで置き換えられた元のコミットは後者に当たります。ただし30日は保証期間ではなく、エントリの日時や設定、`git gc`の実行状況で早く消えることも残ることもあります。確実に残したい履歴は、rebase前に`git branch backup/feature`のようにブランチを作っておくのが安全です。mergeで取り込んだ変更を取り消す方法は[git mergeの取り消し｜abort・reset・revert -mの使い分け](/tech/details/17755/)で扱っています。

## git history：rebase -iを使わずに履歴を直す新コマンド

Git 2.54.0で、実験的な`git history`コマンドが追加されました。`git rebase -i`でtodoリストを編集しなくても、1つのコミットに対する書き換えをサブコマンド1つで行えます。2.56.0時点のサブコマンドは次の4つです。

| サブコマンド | 動作                 | 追加版    |
| ------ | ------------------ | ------ |
| reword | メッセージの書き直し         | 2.54.0 |
| split  | コミットの分割            | 2.54.0 |
| fixup  | ステージした変更を指定コミットへ統合 | 2.55.0 |
| drop   | コミットの削除            | 2.56.0 |

[git-historyのドキュメント](https://git-scm.com/docs/git-history)によると、rebaseとの大きな違いは、フックを実行しないこと、既定で対象コミットの子孫にあたるブランチをすべて新しいコミットへ付け替えることです。別の土台への載せ替えやコミット範囲の並べ替えは、引き続き`git rebase`の役割です。「EXPERIMENTAL」と明記されていて挙動が変わりうるので、チームの手順書に組み込むのはまだ早いと判断しています。手元のGitの版の確認と更新方法は[Gitの最新バージョンは2.56.0｜確認・アップデート方法とGit 3.0の変更点【2026年10月】](/tech/details/10164/)を参照してください。

## よくある質問

### git rebaseとgit mergeはどちらを使うべきですか？

まだpushしていない自分のブランチを最新の`main`に追従させる・コミットを整えるならrebase、他人と共有したブランチの統合や分岐の経緯を残したいならmergeです。共有済みのブランチにはrebaseを使いません。

### rebaseを途中でやめる・完了後に取り消すには？

途中なら`git rebase --abort`で開始前に戻ります。完了後は`git reflog`で`rebase (start)`より前の行を探して`git reset --hard HEAD@{n}`で戻します。`ORIG_HEAD`がrebase前の先端を指していると`git log -1 ORIG_HEAD`で確認できた場合は、`git reset --hard ORIG_HEAD`でも戻せます。

### rebase後にpushがrejectedになるのはなぜですか？

rebaseでコミットIDが変わり、リモートの履歴と食い違うためです。`git push --force-with-lease --force-if-includes`で条件付きの強制pushをします。素の`--force`は他人の追加コミットを消すので避けます。

### git rebase -iのsquashとfixupの違いは？

どちらも直前のコミットへ統合します。squashは両方のメッセージをまとめて編集でき、fixupは自分のメッセージを捨てて直前のメッセージをそのまま使います。

### git pull `--rebase`とは何ですか？

リモートの更新を取り込むときにマージコミットを作らず、手元の未pushコミットを取り込み後の先端へ載せ直すpullです。`git config --global pull.rebase true`で既定にできます。

## 関連記事

- [Gitコマンド一覧｜よく使う基本コマンドの早見表と用途別の使い方・トラブル対処【Git 2.56対応】](/tech/details/2787/)
- [git squashとは？コミットが多すぎる履歴をまとめる手順とスカッシュマージの注意点](/tech/details/2506/)
- [git mergeの取り消し｜abort・reset・revert -mの使い分け](/tech/details/17755/)
- [GitHubのPull Requestの作り方：作成手順とマージ方式・レビュー必須化の決め方](/tech/details/18004/)
- [コンフリクトとは？Gitで変更が衝突する仕組みと解消手順・予防設計を実装視点で解説](/tech/details/13562/)

---

出典: [git rebaseとは？使い方とmergeとの違い・コンフリクト解決・やり直しを実行例で解説](<https://www.issoh.co.jp/tech/details/8926/>)（株式会社一創）
