Git cherry-pick 操作を元に戻す方法

GitBeginner
オンラインで実践に進む

はじめに

Git の cherry-pick 機能を使用すると、あるブランチから別のブランチに特定のコミットを適用できます。強力な機能ですが、エラーや競合が原因で cherry-pick 操作を元に戻す必要がある場合があります。この実験(Lab)では、cherry-pick を実行し、必要に応じてそれを元に戻すためのさまざまな方法を学びます。最終的には、cherry-pick 操作に関する実践的な経験と、一般的な問題から回復するためのスキルを習得できます。

テストリポジトリの設定

このステップでは、cherry-pick 操作を練習するためのテスト Git リポジトリを作成します。これにより、さまざまな Git コマンドを試すための安全な環境が提供されます。

新しい Git リポジトリの作成

まず、テストリポジトリ用の新しいディレクトリを作成し、それを Git リポジトリとして初期化することから始めましょう。

mkdir -p ~/project/cherry-pick-lab
cd ~/project/cherry-pick-lab
git init

次のような出力が表示されるはずです。

Initialized empty Git repository in /home/labex/project/cherry-pick-lab/.git/

Git ユーザー設定のセットアップ

コミットを行う前に、Git のユーザー名とメールアドレスを設定する必要があります。

git config --local user.name "LabEx User"
git config --local user.email "labex@example.com"

メインブランチでの最初のコミットの作成

メインブランチで最初のコミットをいくつか作成しましょう。

## 最初のファイルを作成してコミットします
echo "## Cherry Pick Lab" > README.md
git add README.md
git commit -m "Initial commit with README"

## 2番目のファイルを作成してコミットします
echo "console.log('Hello, world!');" > app.js
git add app.js
git commit -m "Add main application file"

コミット履歴の表示

すべてが正しく設定されていることを確認するために、コミット履歴を確認しましょう。

git log --oneline

次のような出力が表示されるはずです。

abcd123 (HEAD -> main) Add main application file
efgh456 Initial commit with README

実際のコミットハッシュは、システムによって異なります。「Add main application file」のコミットハッシュをメモしておいてください。後で使用します。

機能ブランチの作成

次に、いくつかの追加の変更を加える機能ブランチを作成しましょう。

git checkout -b feature-branch

次のような出力が表示されるはずです。

Switched to a new branch 'feature-branch'

次に、この機能ブランチにいくつかのコミットを追加しましょう。

## 新しい機能ファイルを作成してコミットします
echo "function newFeature() { return 'awesome'; }" > feature.js
git add feature.js
git commit -m "Add new feature function"

## README ファイルを変更します
echo -e "## Cherry Pick Lab\n\nThis repo demonstrates git cherry-pick operations." > README.md
git add README.md
git commit -m "Update README with project description"

これで、2 つのコミットを持つメインブランチと、2 つの追加コミットを持つ機能ブランチができました。次のステップでは、cherry-pick を使用して、機能ブランチのコミットの 1 つをメインブランチに適用します。

Cherry-pick 操作の実行

このステップでは、cherry-pick コマンドを使用して、あるブランチから別のブランチに特定のコミットを適用する方法を学びます。これは、あるブランチから別のブランチへの変更を、選択的に組み込みたい場合に役立ちます。

Cherry-pick の理解

Git の cherry-picking を使用すると、あるブランチから特定のコミットを選択し、それを別のブランチに適用できます。通常複数のコミットを適用するマージやリベースとは異なり、cherry-picking は一度に 1 つのコミットのみを適用します。

メインブランチへの切り替え

まず、機能ブランチからコミットを適用したいメインブランチに切り替えましょう。

git checkout main

切り替えを確認する出力が表示されるはずです。

Switched to branch 'main'

機能ブランチのコミットの表示

cherry-picking を行う前に、メインに適用したい機能ブランチのコミットを調べましょう。

git log feature-branch --oneline

これにより、メインブランチと共有されているものを含め、機能ブランチ内のすべてのコミットが表示されます。次のような出力が表示されます。

1234abc Update README with project description
5678def Add new feature function
abcd123 Add main application file
efgh456 Initial commit with README

「Add new feature function」のコミットハッシュ(この例では 5678def)をメモしておいてください。次のステップでこのハッシュを使用します。

コミットの Cherry-pick

次に、「Add new feature function」コミットを機能ブランチからメインブランチに cherry-pick しましょう。

git cherry-pick [COMMIT_HASH]

[COMMIT_HASH] を、先ほどメモした実際のハッシュに置き換えます。例:

git cherry-pick 5678def

cherry-pick が成功すると、次のような出力が表示されます。

[main 98765ab] Add new feature function
 1 file changed, 1 insertion(+)
 create mode 100644 feature.js

Cherry-pick の検証

cherry-pick が期待どおりに機能したことを確認しましょう。

git log --oneline

これで、cherry-pick されたコミットがメインブランチの履歴に表示されるはずです。

98765ab (HEAD -> main) Add new feature function
abcd123 Add main application file
efgh456 Initial commit with README

また、ファイルが存在することも確認できます。

ls -la

出力に feature.js がリストされているはずです。

total 16
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 .
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 ..
drwxr-xr-x 8 labex labex 4096 Jan 1 00:00 .git
-rw-r--r-- 1 labex labex   29 Jan 1 00:00 app.js
-rw-r--r-- 1 labex labex   42 Jan 1 00:00 feature.js
-rw-r--r-- 1 labex labex   16 Jan 1 00:00 README.md

cherry-pick 操作により、機能ブランチからのコミットがメインブランチに正常に適用されました。次のステップでは、必要に応じてこの cherry-pick を元に戻す方法を学びます。

Git Reset を使用した Cherry-pick の取り消し

cherry-pick を正常に実行したので、この操作を元に戻す方法を学びましょう。このステップでは、最近の cherry-pick を元に戻す最も簡単な方法である git reset コマンドを使用します。

Git Reset の理解

git reset コマンドは、現在のブランチポインタを指定されたコミットに移動し、その時点以降のすべてのコミットを効果的に「元に戻します」。git reset には、主に 3 つのモードがあります。

  • --soft: ブランチポインタを移動しますが、変更はステージングされたままになります
  • --mixed (デフォルト): ブランチポインタを移動し、変更のステージングを解除します
  • --hard: ブランチポインタを移動し、すべての変更を破棄します

cherry-pick を元に戻すために、cherry-pick されたコミットとその変更を完全に削除するために、--hard オプションを使用します。

現在のステータスの確認

まず、現在のステータスを確認して、cherry-pick されたコミットを含むメインブランチにいることを確認しましょう。

git log --oneline -n 3

次のような出力が表示されるはずです。

98765ab (HEAD -> main) Add new feature function
abcd123 Add main application file
efgh456 Initial commit with README

Git Reset を使用した Cherry-pick の取り消し

cherry-pick 操作を元に戻すには、--hard オプションを指定して git reset を使用し、ブランチポインタを 1 つ前のコミットに戻します。

git reset --hard HEAD~1

このコマンドは、Git に対して、現在の HEAD (~1 の部分は「1 つ前のコミット」を意味します) の前のコミットにブランチをリセットするように指示します。

次のような出力が表示されるはずです。

HEAD is now at abcd123 Add main application file

リセットの検証

cherry-pick が元に戻されたことを確認しましょう。

git log --oneline

cherry-pick されたコミットが履歴に表示されなくなったはずです。

abcd123 (HEAD -> main) Add main application file
efgh456 Initial commit with README

feature.js ファイルが削除されたかどうかも確認しましょう。

ls -la

出力には feature.js が含まれていないはずです。

total 12
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 .
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 ..
drwxr-xr-x 8 labex labex 4096 Jan 1 00:00 .git
-rw-r--r-- 1 labex labex   29 Jan 1 00:00 app.js
-rw-r--r-- 1 labex labex   16 Jan 1 00:00 README.md

おめでとうございます!git reset を使用して、cherry-pick 操作を正常に元に戻しました。この方法はクリーンでシンプルですが、履歴を書き換えるため、共有リポジトリにプッシュされていないローカルの変更にのみ使用する必要があります。

Git Revert を使用した Cherry-pick の取り消し

前のステップでは、git reset を使用して cherry-pick を元に戻しました。ただし、git reset は履歴を書き換えるため、変更を既に共有リポジトリにプッシュしている場合は問題が発生する可能性があります。このステップでは、履歴を書き換えることなく、git revert を使用して cherry-pick を安全に元に戻す方法を学びます。

Git Revert の理解

git revert コマンドは、以前のコミットによって導入された変更を元に戻す新しいコミットを作成します。履歴からコミットを削除する git reset とは異なり、git revert は変更に対抗する新しいコミットを追加し、コミット履歴を保持します。

Cherry-pick の再実行

まず、元に戻すものがあるように、コミットをもう一度 cherry-pick しましょう。

## Get the commit hash from the feature branch
FEATURE_HASH=$(git log feature-branch --oneline | grep "new feature" | cut -d ' ' -f 1)

## Cherry-pick the commit
git cherry-pick $FEATURE_HASH

次のような出力が表示されるはずです。

[main 98765ab] Add new feature function
 1 file changed, 1 insertion(+)
 create mode 100644 feature.js

現在のステータスの確認

cherry-pick が成功したことを確認しましょう。

git log --oneline -n 3
ls -la

履歴に cherry-pick されたコミットと、ディレクトリリストに feature.js ファイルが表示されるはずです。

Cherry-pick の Revert

次に、履歴を保持しながら、git revert を使用して cherry-pick を元に戻しましょう。

git revert HEAD --no-edit

--no-edit フラグは、エディタを開かずにデフォルトのコミットメッセージを使用するように Git に指示します。

次のような出力が表示されるはずです。

[main abc9876] Revert "Add new feature function"
 1 file changed, 1 deletion(-)
 delete mode 100644 feature.js

Revert の検証

コミット履歴を確認しましょう。

git log --oneline -n 4

cherry-pick されたコミットと revert コミットの両方が表示されるはずです。

abc9876 (HEAD -> main) Revert "Add new feature function"
98765ab Add new feature function
abcd123 Add main application file
efgh456 Initial commit with README

次に、feature.js ファイルが削除されたかどうかを確認しましょう。

ls -la

出力には feature.js が含まれていないはずです。

total 12
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 .
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 ..
drwxr-xr-x 8 labex labex 4096 Jan 1 00:00 .git
-rw-r--r-- 1 labex labex   29 Jan 1 00:00 app.js
-rw-r--r-- 1 labex labex   16 Jan 1 00:00 README.md

git resetgit revert はどちらも同じ最終結果 (cherry-pick によって導入された変更の削除) を達成しますが、その方法は異なります。

  • git reset は履歴からコミットを削除します。これは、コミットが他のユーザーと共有されている場合に問題を引き起こす可能性があります。
  • git revert は変更を元に戻す新しいコミットを追加し、コミット履歴を保持します。これは、共有リポジトリにとってより安全です。

これらの方法のどちらを選択するかは、特定の状況によって異なります。

  • 共有されていないローカルの変更には git reset を使用します
  • 共有リポジトリにプッシュされた変更には git revert を使用します

Cherry-pick の競合の処理

コミットを cherry-pick すると、コミット内の変更が現在のブランチの変更と競合する場合、Git が競合に遭遇することがあります。このステップでは、cherry-pick の競合を処理する方法と、cherry-pick 操作を中止する方法を学びます。

潜在的な競合が発生するシナリオの作成

まず、cherry-pick の競合が発生するシナリオを作成しましょう。

## main ブランチに切り替えて README.md を変更します
git checkout main
echo -e "## Cherry Pick Lab\n\nThis is the main branch README." > README.md
git commit -am "Update README in main branch"

## feature ブランチに切り替えて、README.md に競合する変更を加えます
git checkout feature-branch
echo -e "## Cherry Pick Lab\n\nThis README has been updated in the feature branch." > README.md
git commit -am "Update README in feature branch"

競合を伴う Cherry-pick の試行

次に、main ブランチに戻り、feature ブランチからコミットを cherry-pick してみましょう。

git checkout main
CONFLICT_HASH=$(git log feature-branch --oneline | grep "Update README in feature" | cut -d ' ' -f 1)
git cherry-pick $CONFLICT_HASH

両方のブランチが README.md の同じ行を変更したため、競合が表示されるはずです。

error: could not apply a1b2c3d... Update README in feature branch
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

競合の表示

競合を調べてみましょう。

git status

README.md での競合を示す出力が表示されるはずです。

On branch main
You are currently cherry-picking commit a1b2c3d.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
  both modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

競合したファイルの内容を見てみましょう。

cat README.md

次のようなものが表示されるはずです。

## Cherry Pick Lab

<<<<<<< HEAD
This is the main branch README.
=======
This README has been updated in the feature branch.
>>>>>>> a1b2c3d... Update README in feature branch

競合の解決

競合を解決するには、ファイルを編集し、どの変更を保持するかを決定する必要があります。README.md を変更して、両方の変更を含めましょう。

echo -e "## Cherry Pick Lab\n\nThis is the main branch README.\n\nThis README has also been updated with content from the feature branch." > README.md

次に、競合を解決済みとしてマークし、cherry-pick を続行しましょう。

git add README.md
git cherry-pick --continue

Git は、デフォルトのコミットメッセージでエディタを開きます。エディタを保存して閉じ、cherry-pick を完了します。

Cherry-pick の中止

場合によっては、競合を解決したくない場合があり、cherry-pick 操作をキャンセルしたい場合があります。別の競合を作成し、cherry-pick を中止しましょう。

## feature ブランチで別の競合するコミットを作成します
git checkout feature-branch
echo "// This will conflict with app.js in main" > app.js
git commit -am "Modify app.js in feature branch"

## このコミットを main に cherry-pick してみます
git checkout main
ANOTHER_CONFLICT=$(git log feature-branch --oneline | grep "Modify app.js" | cut -d ' ' -f 1)
git cherry-pick $ANOTHER_CONFLICT

別の競合が表示されるはずです。今回は、cherry-pick を中止しましょう。

git cherry-pick --abort

cherry-pick 操作がキャンセルされ、作業ディレクトリが以前の状態に復元されたことがわかるはずです。

git status

出力:

On branch main
nothing to commit, working tree clean

cherry-pick 操作中の競合の処理は、Git ユーザーにとって不可欠なスキルです。競合が発生した場合は、次の 3 つのオプションがあります。

  1. 競合を手動で解決し、git addgit cherry-pick --continue を使用します
  2. 競合するコミットを git cherry-pick --skip でスキップします
  3. git cherry-pick --abort で cherry-pick 操作全体を中止します

最適なアプローチは、特定の状況とプロジェクトの要件によって異なります。

まとめ

この実験では、Git の cherry-pick 機能に関する実践的な経験を積み、cherry-pick 操作を元に戻す複数の方法を学びました。

  1. 練習用に、複数のブランチとコミットを持つテストリポジトリを作成しました。
  2. あるブランチから別のブランチに特定のコミットを適用するために、cherry-pick 操作を実行しました。
  3. ローカルの変更に適した git reset を使用して cherry-pick を元に戻す方法を学びました。
  4. 履歴を保持する git revert を使用して、cherry-pick を安全に元に戻す方法を調べました。
  5. cherry-pick の競合を処理する練習をし、cherry-pick 操作を中止する方法を学びました。

これらのスキルは、実際のプロジェクトで Git を使用する際に非常に役立ちます。Cherry-picking を使用すると、ブランチ間で変更を選択的に適用できます。また、cherry-pick を元に戻す方法を知っておくことで、誤りから回復し、クリーンな Git 履歴を維持できます。

さまざまな元に戻す方法が異なる目的に役立つことを覚えておいてください。

  • 共有されていないローカルの変更には git reset を使用します
  • 共有リポジトリにプッシュされた変更には git revert を使用します
  • 進行中の cherry-pick 操作をキャンセルするには、git cherry-pick --abort を使用します

これらのオプションを理解することで、特定の状況に最適な方法を選択できます。