Git を使用して、最後の X コミットを 1 つのコミットにまとめるにはどうすればよいですか?
40 に答える
git rebase
またはなしで、これをかなり簡単に行うことができますgit merge --squash
。この例では、最後の 3 つのコミットをスカッシュします。
新しいコミットメッセージを最初から書きたい場合は、これで十分です:
git reset --soft HEAD~3 &&
git commit
既存のコミット メッセージを連結して新しいコミット メッセージの編集を開始する場合 (つまり、pick/squash/squash/…/squashgit rebase -i
命令リストで開始するものと同様)、それらのメッセージを抽出して渡す必要があります。それらにgit commit
:
git reset --soft HEAD~3 &&
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
これらの方法はどちらも、最後の 3 つのコミットを同じ方法で 1 つの新しいコミットに押しつぶします。ソフトリセットは、スカッシュしたくない最後のコミットに HEAD を再ポイントするだけです。インデックスも作業ツリーもソフト リセットの影響を受けず、インデックスは新しいコミットに必要な状態のままになります (つまり、「破棄」しようとしているコミットからのすべての変更が既に含まれています)。
マニュアルの説明に従って、2 回目以降のコミットで「pick」を使用git rebase -i <after-this-commit>
し、「squash」または「fixup」に置き換えます。
この例で<after-this-commit>
は、現在のブランチの HEAD からの SHA1 ハッシュまたは相対位置であり、そこから rebase コマンドのコミットが分析されます。たとえば、ユーザーが過去の現在の HEAD から 5 つのコミットを表示したい場合、コマンドはgit rebase -i HEAD~5
.
これはgit merge --squash
、 よりもわずかにエレガントですgit rebase -i
。master 上にいて、最後の 12 個のコミットを 1 つにまとめたいとします。
警告: まず、作業をコミットすることを確認してください —git status
クリーンであることを確認してください (git reset --hard
ステージングされた変更とステージングされていない変更を破棄するため)
それで:
# Reset the current branch to the commit just before the last 12:
git reset --hard HEAD~12
# HEAD@{1} is where the branch was just before the previous command.
# This command sets the state of the index to be as it would just
# after a merge from that commit:
git merge --squash HEAD@{1}
# Commit those squashed changes. The commit message will be helpfully
# prepopulated with the commit messages of all the squashed commits:
git commit
のドキュメントにgit merge
--squash
、オプションの詳細が記載されています。
更新:git reset --soft HEAD~12 && git commit
Chris Johnsenの回答で提案された単純な方法に対するこの方法の唯一の本当の利点は、押しつぶしているすべてのコミット メッセージが事前に入力されたコミット メッセージを取得することです。
git reset
特に Git 初心者の場合は、可能な限り避けることをお勧めします。多数のコミットに基づいてプロセスを自動化する必要が本当にない限り、あまりエキゾチックではない方法があります...
- 押しつぶす予定のコミットを作業ブランチに置きます (まだ作業ブランチにない場合) -- これには gitk を使用します
- ターゲット ブランチをチェックアウトします (例: 'master')
git merge --squash (working branch name)
git commit
コミット メッセージは、スカッシュに基づいて事前設定されます。
この便利なブログ投稿のおかげで、このコマンドを使用して最後の 3 つのコミットをスカッシュできることがわかりました。
git rebase -i HEAD~3
これは、追跡情報やリモート リポジトリのないローカル ブランチにいる場合でも機能するので便利です。
このコマンドはインタラクティブなリベース エディターを開き、通常どおりに並べ替え、スカッシュ、リワードなどを行うことができます。
インタラクティブなリベース エディターを使用する:
インタラクティブなリベース エディターには、最後の 3 つのコミットが表示されます。この制約はHEAD~3
、コマンドの実行時に決定されましたgit rebase -i HEAD~3
。
最新のコミットHEAD
が最初に 1 行目に表示されます。a で始まる行#
はコメント/ドキュメントです。
表示されるドキュメントは非常に明確です。任意の行で、コマンドを からpick
選択したコマンドに変更できます。
fixup
このコマンドは、コミットの変更を上記の行のコミットに「押しつぶし」、コミットのメッセージを破棄するため、このコマンドを使用することを好みます。
1 行目のコミットは であるHEAD
ため、ほとんどの場合、これをそのままにしておきますpick
。コミットを押しつぶす他のコミットがないためsquash
、 orを使用することはできません。fixup
コミットの順序を変更することもできます。これにより、時系列的に隣接していないコミットをスカッシュまたは修正できます。
実用的な日常の例
最近、新しい機能をコミットしました。それ以来、2 つのバグ修正をコミットしました。しかし今、コミットした新機能にバグ (または単なるスペルミス) を発見しました。うっとうしい!コミット履歴を汚染する新しいコミットはしたくありません!
私が最初にすることは、間違いを修正し、コメントで新しいコミットを作成することsquash this into my new feature!
です。
次にgit log
orgitk
を実行して、新しい機能のコミット SHA を取得します (この場合は1ff9460
)。
次に、インタラクティブなリベース エディターを立ち上げgit rebase -i 1ff9460~
ます。コミット後、SHA はエディターにその~
コミットをエディターに含めるように指示します。
次に、修正を含むコミット ( fe7f1e0
) を機能コミットの下に移動し、 に変更pick
しfixup
ます。
エディターを閉じると、修正が機能のコミットに押しつぶされ、コミット履歴がきれいに表示されます。
これは、すべてのコミットがローカルの場合はうまく機能しますが、既にリモートにプッシュされているコミットを変更しようとすると、同じブランチをチェックアウトした他の開発者に問題が発生する可能性があります!
この記事に基づいて、この方法が私のユースケースにとってより簡単であることがわかりました。
私の 'dev' ブランチは 'origin/dev' よりも 96 コミット進んでいました (したがって、これらのコミットはまだリモートにプッシュされていません)。
変更をプッシュする前に、これらのコミットを 1 つにまとめたかったのです。ブランチを「origin/dev」の状態にリセットし (これにより、96 個のコミットからのすべての変更がステージングされないままになります)、変更を一度にコミットすることをお勧めします。
git reset origin/dev
git add --all
git commit -m 'my commit message'
feature-branch
Golden Repository( ) から複製されたリモート ブランチ ( と呼ばれる) を使用している場合golden_repo_name
、コミットを 1 つにまとめる方法は次のとおりです。
ゴールデンレポをチェックアウト
git checkout golden_repo_name
次のように、それ(ゴールデンレポ)から新しいブランチを作成します
git checkout -b dev-branch
既に持っているローカル ブランチとのマージをスカッシュします。
git merge --squash feature-branch
変更をコミットします (これが dev-branch に入る唯一のコミットになります)
git commit -m "My feature complete"
ブランチをローカル リポジトリにプッシュする
git push origin dev-branch
すべてのコミットを 1 つのコミットにまとめたい場合(たとえば、プロジェクトを初めて公開する場合)、次のことを試してください。
git checkout --orphan <new-branch>
git commit
これは非常に不器用ですが、一種のクールな方法なので、リングに放り込みます。
GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo
翻訳:編集するファイル名がgit-rebase-todo
(インタラクティブなリベースプロンプト)である場合、最初の「ピック」以外のすべてを「スカッシュ」に変更し、そうでない場合はvimを生成する新しい「エディター」をgitに提供します-プロンプトが表示されたときに押しつぶされたコミット メッセージを編集するには、vim を取得します。(そして明らかに、私はブランチ foo の最後の 5 つのコミットを押しつぶしていましたが、好きなように変更できます。)
私はおそらくマーク・ロングエアが提案したことをするだろう.
より一般的な解決策は、「N」コミットを指定するのではなく、スカッシュしたいブランチ/コミット ID を指定することです。これは、特定のコミットまでのコミットをカウントするよりもエラーが発生しにくくなります。タグを直接指定するか、本当にカウントしたい場合は、HEAD~N を指定できます。
私のワークフローでは、ブランチを開始し、そのブランチでの最初のコミットで目標が要約されます (つまり、これは通常、機能の「最終」メッセージとしてパブリック リポジトリにプッシュするものです)。私がやりたいことはgit squash master
、最初のメッセージに戻って、プッシュする準備ができたことです。
私はエイリアスを使用します:
squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i
これにより、押しつぶされた履歴がダンプされる前にダンプされます。これにより、元に戻したい場合に、コンソールから古いコミット ID を取得して復元する機会が得られます。(Solaris ユーザーは、GNU sed-i
オプションを使用していることに注意してください。Mac および Linux ユーザーはこれで問題ないはずです。)
同じブランチのコミットにリベースするときにマージの競合を解決する必要がないようにするには、次のコマンドを使用できます。
git rebase -i <last commit id before your changes start> -s recursive -X ours
すべてのコミットを 1 つにまとめるには、マージするコミットを編集するように求められたら (-i フラグ)、他の回答でも推奨されているように、最初のアクションを除くすべてを更新しますpick
。squash
ここでは、マージ戦略 (-s フラグ)recursive
と戦略オプション (-X)ours
を使用して、履歴内の後のコミットがマージ競合に勝つようにします。
注git rebase -s ours
: これを他の何かを行うものと混同しないでください。
まず、フィーチャー ブランチと現在のマスター ブランチの間のコミット数を確認します。
git checkout master
git rev-list master.. --count
次に、my-feature ブランチに基づいて別のブランチを作成し、ブランチはそのままにしmy-feature
ます。
最後に、私は走ります
git checkout my-feature
git checkout -b my-rebased-feature
git checkout master
git checkout my-rebased-feature
git rebase master
git rebase head^x -i
// fixup/pick/rewrite
git push origin my-rebased-feature -f // force, if my-rebased-feature was ever pushed, otherwise no need for -f flag
// make a PR with clean history, delete both my-feature and my-rebased-feature after merge
役に立てば幸いです、ありがとう。