web-dev-qa-db-ja.com

特定のコミットを変更する方法

私は通常レビューのためにコミットのリストを提出します。以下のコミットがあるとします。

  1. HEAD
  2. Commit3
  3. Commit2
  4. Commit1

...ヘッドコミットをgit commit --amendで修正できることを私は知っています。しかし、それがHEADコミットではない場合、どうすればCommit1を変更できますか?

1947
Sam Liao

例えば、git rebaseを使ってcommit bbc643cdに戻したい場合は、

$ git rebase --interactive 'bbc643cd^'

デフォルトのエディタで、コミットを変更したい行のpickeditに変更します。変更を加えてから、以前と同じメッセージでそれらをコミットします。

$ git commit --all --amend --no-edit

コミットを変更し、その後

$ git rebase --continue

直前のヘッドコミットに戻ります。

WARNING:これはそのコミットのSHA-1とすべての子を変更することに注意してください。これはそれ以降の歴史を書き換えます。 コマンドgit Push --forceを使ってプッシュすると、これを行っているリポジトリを壊すことができます

2537
ZelluX

素晴らしいインタラクティブリベースを使ってください。

git rebase -i @~9   # Show the last 9 commits in a text editor

必要なコミットを見つけ、pickeedit)に変更して、ファイルを保存して閉じます。 Gitはそのコミットに巻き戻します。

  • git commit --amendを使って変更する
  • 最後のコミットを破棄するにはgit reset @~を使用しますが、ファイルへの変更は破棄しません(つまり、ファイルを編集した時点でまだコミットされていない時点に移動します)。

後者は複数のコミットに分割するようなもっと複雑なことをするのに役立ちます。

それからgit rebase --continueを実行すると、Gitはあなたの修正されたコミットに加えてその後の変更を再生します。マージの衝突を修正するよう求められるかもしれません。

注:@HEADの省略形で、~は指定されたコミットの前のコミットです。

歴史を書き換える についてもっと読むにはGitのドキュメントを参照してください。


リベースを恐れてはいけません

ProTip™:履歴を書き換える「危険な」コマンドを試すことを恐れないでください* - Gitはデフォルトで90日間コミットを削除しません。あなたはそれらをreflogで見つけることができます:

$ git reset @~3   # go back 3 commits
$ git reflog
c4f708b HEAD@{0}: reset: moving to @~3
2c52489 HEAD@{1}: commit: more changes
4a5246d HEAD@{2}: commit: make important changes
e8571e4 HEAD@{3}: commit: make some changes
... earlier commits ...
$ git reset 2c52489
... and you're back where you started

*--hard--forceのようなオプションには気をつけてください - データを捨てることもできます
*また、協力しているブランチの履歴を書き換えないでください。



多くのシステムで、git rebase -iはデフォルトでVimを起動します。 Vimは最近のほとんどのテキストエディタのようには動作しないので、 Vim を使ってリベースする方法を見てください。別のエディタを使用したい場合は、git config --global core.editor your-favorite-text-editorで変更してください。

380
Zaz

対話的な リベース--autosquashは、以前のコミットをもっと深く修正する必要があるときによく使うものです。それは本質的にZelluXの答えが示すプロセスをスピードアップします、そしてあなたが編集する必要がある複数のコミットがあるとき特に便利です。

ドキュメントから:

--autosquash

コミットログメッセージが "squash!…"(または "fixup!…")で始まり、タイトルが同じ…で始まるコミットがある場合は、コミットが完了するようにrebase -iのToDoリストを自動的に変更します。スカッシュマークが付けられたものは、変更のコミットの直後に表示されます。

このような歴史があるとしましょう。

$ git log --graph --oneline
* b42d293 Commit3
* e8adec4 Commit2
* faaf19f Commit1

そして、あなたはあなたがCommit2に修正したい変更を持っていてそれからあなたの変更をコミットする

$ git commit -m "fixup! Commit2"

あるいは、コミットメッセージの代わりにcommit-shaを使用することもできます。そのため"fixup! e8adec4またはコミットメッセージの接頭辞だけでもかまいません。

それからコミットの前に対話的なリベースを開始します

$ git rebase e8adec4^ -i --autosquash

あなたのエディタはすでに正しく注文されたコミットで開きます。

pick e8adec4 Commit2
fixup 54e1a99 fixup! Commit2
pick b42d293 Commit3

あなたがする必要があるのは保存して終了することだけです

67
thrau

実行します。

$ git rebase --interactive commit_hash^

それぞれの^は編集したいコミットの数を示し、それが1つだけの場合(指定したコミットハッシュ)、^を1つだけ追加します。

Vimを使って、変更したいコミットをpickからrewordに変更し、保存して終了します(:wq)。それからgitはあなたがrewordとしてマークした各コミットであなたを促しますので、コミットメッセージを変更することができます。

次のコミットメッセージに進むには、コミットメッセージを保存して終了する必要があります(:wq)。

変更を適用せずに終了したい場合は:q!を押してください。

EDITvim内を移動するにはjを使用し、上に移動するにはkを使用し、hを使用する左に移動し、lモードに移動するにはNORMALモードに移動し、ESCモードに移動するにはNORMALを押します。テキストを編集するには、iを押してテキストを挿入するINSERTモードに入ります。 ESCモードに戻るにはNORMALを押してください:)

UPDATE:これはgithubのリストからの素晴らしいリンクです gitで(ほとんど)何でも元に戻す方法

35
betoharres

何らかの理由でインタラクティブエディタが気に入らない場合は、git rebase --ontoを使用できます。

Commit1を修正したいとしましょう。最初に、からCommit1の前に分岐します。

git checkout -b amending [commit before Commit1]

次に、Commit1cherry-pickで取得します。

git cherry-pick Commit1

それでは、Commit1'を作成して、あなたの変更を修正しましょう。

git add ...
git commit --amend -m "new message for Commit1"

そして最後に、他の変更を隠した後、あなたの新しいコミットの上に残りのコミットをmasterまで移植します。

git rebase --onto amending Commit1 master

読んでください: "amendingへのリベース、すべてのCommit1(包括的)とmaster(包括的)の間のコミット"つまり、Commit2とCommit3で、古いCommit1を完全に削除します。あなたはそれらをチェリーピックすることができますが、この方法はより簡単です。

あなたの枝を片付けるのを忘れないでください!

git branch -d amending
17
FeepingCreature

完全に非インタラクティブなコマンド(1)

私はこれに使用しているエイリアスを共有すると思いました。それは非対話型対話型リベースに基づいています。それをあなたのgitに追加するには、このコマンドを実行してください(以下の説明を参照)。

git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"; }; f'

このコマンドの最大の利点は、no-vimという事実です。


(1)もちろん、リベース中に競合がないことを考えれば、

使用法

git amend-to <REV> # e.g.
git amend-to HEAD~1
git amend-to aaaa1111

amend-toという名前は適切なようです。フローを--amendと比較します。

git add . && git commit --amend --no-edit
# vs
git add . && git amend-to <REV>

説明

  • git config --global alias.<NAME> '!<COMMAND>' - git以外のコマンドを実行する<NAME>という名前のグローバルgitエイリアスを作成します<COMMAND>
  • f() { <BODY> }; f - 「匿名」のbash関数.
  • SHA=`git rev-parse "$1"`; - 引数をgit revisionに変換し、結果を変数SHAに代入します
  • git commit --fixup "$SHA" - SHAに対するfixup-commit git-commit docs を参照してください。
  • GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"
    • git rebase --interactive "$SHA^"の部分は他の答えでカバーされています。
    • --autosquashgit commit --fixupと一緒に使われるものです。詳細は git-rebase docs
    • GIT_SEQUENCE_EDITOR=trueは全体を非インタラクティブにするものです。このハックは私がこのブログ投稿から学んだ です。
10
Dethariel

自動対話式リベース編集とそれに続くコミットの取り消しにより、やり直しの準備が整います

私は自分自身のためにスクリプトを書いたのに十分な頻度で過去のコミットを直していました。

これがワークフローです。

  1. git commit-edit <commit-hash>
    

    これはあなたが編集したいコミットであなたをドロップします。

  2. 最初にコミットされたことを望み通りに修正してステージングします。

    (コミットしていないファイルを保存するためにgit stash saveを使用することをお勧めします)

  3. --amendでコミットをやり直してください。例えば:

    git commit --amend
    
  4. リベースを完了します。

    git rebase --continue
    

上記が機能するためには、以下のスクリプトをgit-commit-editのどこかにある$PATHという実行可能ファイルに入れてください。

#!/bin/bash

set -euo pipefail

script_name=${0##*/}

warn () { printf '%s: %s\n' "$script_name" "$*" >&2; }
die () { warn "$@"; exit 1; }

[[ $# -ge 2 ]] && die "Expected single commit to edit. Defaults to HEAD~"

# Default to editing the parent of the most recent commit
# The most recent commit can be edited with `git commit --amend`
commit=$(git rev-parse --short "${1:-HEAD~}")
message=$(git log -1 --format='%h %s' "$commit")

if [[ $OSTYPE =~ ^darwin ]]; then
  sed_inplace=(sed -Ei "")
else
  sed_inplace=(sed -Ei)
fi

export GIT_SEQUENCE_EDITOR="${sed_inplace[*]} "' "s/^pick ('"$commit"' .*)/edit \\1/"'
git rebase --quiet --interactive --autostash --autosquash "$commit"~
git reset --quiet @~ "$(git rev-parse --show-toplevel)"  # Reset the cache of the toplevel directory to the previous commit
git commit --quiet --amend --no-edit --allow-empty  #  Commit an empty commit so that that cache diffs are un-reversed

echo
echo "Editing commit: $message" >&2
echo
7
Tom Hale

このアプローチに来ました(そしてそれはおそらく対話式リベースを使うのと全く同じです)が、私にとってはそれは一種の簡単です。

注:このアプローチは、日常的な方法ではなく、あなたができることを説明するために提示したものです。それは多くのステップを持っているので(そしておそらくいくつかの注意点があります。)

コミット0を変更したいとし、現在feature-branchを実行しているとします。

some-commit---0---1---2---(feature-branch)HEAD

このコミットをチェックアウトしてquick-branchを作成してください。機能ブランチをリカバリポイントとして複製することもできます(開始前)。

?(git checkout -b feature-branch-backup)
git checkout 0
git checkout -b quick-branch

これで、こんな感じになります。

0(quick-branch)HEAD---1---2---(feature-branch)

ステージの変更は、他のすべてを隠します。

git add ./example.txt
git stash

変更をコミットしてfeature-branchにチェックアウトする

git commit --amend
git checkout feature-branch

これで、こんな感じになります。

some-commit---0---1---2---(feature-branch)HEAD
           \
             ---0'(quick-branch)

feature-branchquick-branchに再配置します(途中で競合があれば解決します)。 stashを適用してquick-branchを削除してください。

git rebase quick-branch
git stash pop
git branch -D quick-branch

そして、あなたは次のようになります。

some-commit---0'---1'---2'---HEAD(feature-branch)

Gitは、リベース時に0コミットを複製することはありません(実際にはどの程度まで言えないか)。

注意:すべてのコミットハッシュは、最初に変更しようとしていたコミットから変更されます。

7
Olga

ドキュメントに基づく

古いコミットメッセージまたは複数のコミットメッセージのメッセージを修正する

git rebase -i HEAD~3 

上記は現在のブランチで最後の3つのコミットのリストを表示します、もっと欲しいなら3を他の何かに変更してください。リストは次のようになります。

pick e499d89 Delete CNAME
pick 0c39034 Better README
pick f7fde4a Change the commit message but Push the same commit.

変更したい各コミットメッセージの前に、pickrewordで置き換えます。リストの2番目のコミットを変更したとすると、ファイルは次のようになります。

pick e499d89 Delete CNAME
reword 0c39034 Better README
pick f7fde4a Change the commit message but Push the same commit.

コミットリストファイルを保存して閉じると、コミットメッセージを変更し、コミットメッセージを変更して保存するための新しいエディタがポップアップ表示されます。

Finaly Force - 修正されたコミットを押します。

git Push --force
6
justMe

非対話型のコマンドを取得するには、この内容のスクリプトをPATHに入れます。

#!/bin/sh
#
# git-fixup
# Use staged changes to modify a specified commit
set -e
cmt=$(git rev-parse $1)
git commit --fixup="$cmt"
GIT_EDITOR=true git rebase -i --autosquash "$cmt~1"

変更を(git addで)ステージングして使用してからgit fixup <commit-to-modify>を実行します。もちろん、あなたが衝突を起こしたとしてもそれは依然として対話型です。

6
Pelle Nilsson

私はこれを解決しました、

1)変更を加えた新しいコミットを作成します。

r8gs4r commit 0

2)私はそれをマージする必要があるコミットを知っています。これはコミット3です。

そのため、git rebase -i HEAD~4#4は最近の4回のコミットを表します(ここではコミット3が4位にあります)

3)インタラクティブリベースでは最近のコミットが一番下に配置されます。それは似ているでしょう、

pick q6ade6 commit 3
pick vr43de commit 2
pick ac123d commit 1
pick r8gs4r commit 0

4)特定のコミットとマージしたい場合は、ここでコミットを再配置する必要があります。それはのようなはずです、

parent
|_child

pick q6ade6 commit 3
f r8gs4r commit 0
pick vr43de commit 2
pick ac123d commit 1

再配置後、ppickffixupはコミットメッセージなしでマージされます)またはssquashコミットメッセージとマージすると実行時に変更される可能性があります)

それからあなたの木を保存し​​てください。

今マージは既存のコミットで行われました。

注:自分で保守しない限り、その方法は好ましくありません。チームサイズが大きい場合、gitツリーを書き換える方法として受け入れられないので、他の人にはわかりませんが競合が発生します。あなたがより少ないコミットであなたの木をきれいにしておきたいならこれを試みることができます、そして、その小さいチームがそうでなければそれは好まれません.....

git stash + rebase自動化

Gerritレビューのために古いコミットを何度も修正する必要があるときは、次のようにしています。

git-amend-to() (
  # Stash, apply to past commit, and rebase the current branch on to of the result.
  current_branch="$(git rev-parse --abbrev-ref HEAD)"
  apply_to="$1"
  git stash
  git checkout "$apply_to"
  git stash apply
  git add -u
  git commit --amend --no-edit
  new_sha="$(git log --format="%H" -n 1)"
  git checkout "$current_branch"
  git rebase --onto "$new_sha" "$apply_to"
)

使用法:

  • ファイルを修正する
  • git-amend-to $old_sha

私は--autosquashよりもこれが好きです。それは他の無関係な修正を潰さないからです。

私にとっては、リポジトリから資格情報を削除するためのものでした。リベースしようとすると、リベースしようとすると途中で無関係の衝突が大量に発生しました - 続行します。自分でリベースしようとしなくても、MacではBFG(brew install bfg)というツールを使用してください。

2
Pellet

まだコミットをプッシュしていない場合は、git reset HEAD^[1,2,3,4...]を使用して前のコミットに戻ることができます。

例えば

git commit <file1> -m "Updated files 1 and 2"
git commit <file3> -m "Updated file 3"

おっと、最初のコミットにfile2を追加するのを忘れていました...

git reset HEAD^1 // because I only need to go back 1 commit

git add <file2>

これはfile2を最初のコミットに追加します。

0
rharvey

まあ、このソリューションは非常に愚かに聞こえるかもしれませんが、特定の条件であなたを救うことができます。

私の友人が巨大なファイル(3GBから5GBまでの範囲の4つの自動生成ファイル)を誤ってコミットし、それに加えていくつかの追加のコードをコミットすることに気付いたgit Pushがもう動かなくなったという問題!

ファイルは.gitignoreにリストされていましたが、コンテナフォルダの名前を変更した後、それらは公開されコミットされました。その上、コードのコミットがさらにいくつかありましたが、Pushは永遠に実行され(GBのデータをアップロードしようとしています)、最後に Githubのファイルサイズ制限により失敗します .

対話的なリベースなどの問題は、これらの巨大なファイルを探し回ることに対処し、何もするのにずっと時間がかかるということでした。それにもかかわらず、CLIで1時間近く費やした後、ファイル(およびデルタ)が実際に履歴から削除されているのか、それとも単に現在のコミットに含まれていないのかはわかりませんでした。プッシュも機能していなかったし、私の友人は本当に立ち往生していました。

だから、私が思い付いた解決策は次のとおりです。

  1. 現在のgitフォルダの名前を~/Project-oldに変更します。
  2. Gitubからgitフォルダを再度クローンします(~/Project)。
  3. 同じブランチにチェックアウトしてください。
  4. 手動でcp -rフォルダーから~/Project-oldにファイルを~/Project
  5. チェックインする必要のない大容量ファイルがmvedであり、.gitignoreに正しく含まれていることを確認してください。
  6. また、最近クローンした.git~/Projectフォルダーを古いもので上書きしないようにしてください。それが問題のある歴史の記録です。
  7. 今すぐ変更を確認します。問題のあるファイルを除いて、それは最近のすべてのコミットの結合です。
  8. 最後に変更をコミットしてください、そしてそれはPushされることは良いことです。

この解決方法の最大の問題は、手動でファイルをコピーすることと、最近行われたすべてのコミットを1つにマージすることです(明らかに新しいコミットハッシュで)。

大きな利点は、すべてのステップで非常に明確であることです。巨大なファイル(および機密性の高いファイル)にも最適です。

0
Aidin