如何撤销git cherry-pick操作

GitGitBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

Introduction

Git's cherry-pick feature allows you to apply specific commits from one branch to another. While powerful, sometimes you might need to reverse a cherry-pick operation due to errors or conflicts. In this lab, you'll learn how to perform a cherry-pick and then use various methods to undo it when necessary. By the end, you'll have practical experience with cherry-pick operations and the skills to recover from common issues.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL git(("Git")) -.-> git/BasicOperationsGroup(["Basic Operations"]) git(("Git")) -.-> git/DataManagementGroup(["Data Management"]) git(("Git")) -.-> git/SetupandConfigGroup(["Setup and Config"]) git(("Git")) -.-> git/BranchManagementGroup(["Branch Management"]) git/SetupandConfigGroup -.-> git/config("Set Configurations") git/SetupandConfigGroup -.-> git/init("Initialize Repo") git/BasicOperationsGroup -.-> git/add("Stage Files") git/BasicOperationsGroup -.-> git/status("Check Status") git/BasicOperationsGroup -.-> git/commit("Create Commit") git/DataManagementGroup -.-> git/reset("Undo Changes") git/BranchManagementGroup -.-> git/checkout("Switch Branches") git/BranchManagementGroup -.-> git/log("Show Commits") git/BranchManagementGroup -.-> git/cherry_pick("Cherry Pick") subgraph Lab Skills git/config -.-> lab-417333{{"如何撤销git cherry-pick操作"}} git/init -.-> lab-417333{{"如何撤销git cherry-pick操作"}} git/add -.-> lab-417333{{"如何撤销git cherry-pick操作"}} git/status -.-> lab-417333{{"如何撤销git cherry-pick操作"}} git/commit -.-> lab-417333{{"如何撤销git cherry-pick操作"}} git/reset -.-> lab-417333{{"如何撤销git cherry-pick操作"}} git/checkout -.-> lab-417333{{"如何撤销git cherry-pick操作"}} git/log -.-> lab-417333{{"如何撤销git cherry-pick操作"}} git/cherry_pick -.-> lab-417333{{"如何撤销git cherry-pick操作"}} end

Setting Up a Test Repository

In this step, we'll create a test Git repository to practice cherry-pick operations. This will provide a safe environment to experiment with various Git commands.

Creating a New Git Repository

Let's start by creating a new directory for our test repository and initializing it as a Git repository:

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

You should see output similar to:

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

Setting Up Git User Configuration

Before we can make commits, we need to set up a user name and email for Git:

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

Creating Initial Commits on the Main Branch

Let's create some initial commits on the main branch:

## Create and commit the first file
echo "## Cherry Pick Lab" > README.md
git add README.md
git commit -m "Initial commit with README"

## Create and commit a second file
echo "console.log('Hello, world!');" > app.js
git add app.js
git commit -m "Add main application file"

Viewing the Commit History

Let's check our commit history to make sure everything is set up correctly:

git log --oneline

You should see output similar to:

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

The actual commit hashes will be different on your system. Take note of the commit hash for "Add main application file" as we'll use it later.

Creating a Feature Branch

Now, let's create a feature branch where we'll make some additional changes:

git checkout -b feature-branch

You should see output similar to:

Switched to a new branch 'feature-branch'

Now let's add some commits to this feature branch:

## Create and commit a new feature file
echo "function newFeature() { return 'awesome'; }" > feature.js
git add feature.js
git commit -m "Add new feature function"

## Modify the README file
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"

Now we have a main branch with two commits and a feature branch with two additional commits. In the next step, we'll use cherry-pick to apply one of the feature branch commits to the main branch.

Performing a Cherry-pick Operation

In this step, we'll learn how to use the cherry-pick command to apply a specific commit from one branch to another. This is useful when you want to selectively incorporate changes from one branch to another.

Understanding Cherry-pick

Cherry-picking in Git allows you to pick a specific commit from one branch and apply it to another branch. Unlike merging or rebasing which typically apply multiple commits, cherry-picking applies just one commit at a time.

Switching Back to the Main Branch

First, let's switch back to the main branch where we want to apply a commit from the feature branch:

git checkout main

You should see output confirming the switch:

Switched to branch 'main'

Viewing the Feature Branch Commits

Before cherry-picking, let's examine the commits in the feature branch that we might want to apply to main:

git log feature-branch --oneline

This will show all commits in the feature branch, including those shared with the main branch. You'll see output similar to:

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

Take note of the commit hash for "Add new feature function" (in this example, it's 5678def). We'll use this hash in the next step.

Cherry-picking a Commit

Now let's cherry-pick the "Add new feature function" commit from the feature branch into our main branch:

git cherry-pick [COMMIT_HASH]

Replace [COMMIT_HASH] with the actual hash you noted earlier. For example:

git cherry-pick 5678def

If the cherry-pick is successful, you'll see output similar to:

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

Verifying the Cherry-pick

Let's confirm that the cherry-pick worked as expected:

git log --oneline

You should now see the cherry-picked commit in your main branch's history:

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

You can also verify that the file exists:

ls -la

You should see feature.js listed in the output:

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

The cherry-pick operation has successfully applied the commit from the feature branch to the main branch. In the next step, we'll learn how to undo this cherry-pick if needed.

Undoing a Cherry-pick with Git Reset

Now that we've successfully cherry-picked a commit, let's learn how to undo this operation. In this step, we'll use the git reset command, which is the most straightforward way to undo a recent cherry-pick.

Understanding Git Reset

The git reset command moves the current branch pointer to a specified commit, effectively "undoing" any commits that came after that point. There are three main modes of git reset:

  • --soft: Moves the branch pointer but leaves changes staged
  • --mixed (default): Moves the branch pointer and unstages changes
  • --hard: Moves the branch pointer and discards all changes

For undoing a cherry-pick, we'll use the --hard option to completely remove the cherry-picked commit and its changes.

Checking the Current Status

First, let's check our current status to confirm we're on the main branch with the cherry-picked commit:

git log --oneline -n 3

You should see output similar to:

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

Undoing the Cherry-pick with Git Reset

To undo the cherry-pick operation, we'll use git reset with the --hard option to move the branch pointer back one commit:

git reset --hard HEAD~1

This command tells Git to reset the branch to the commit before the current HEAD (the ~1 part means "one commit before").

You should see output similar to:

HEAD is now at abcd123 Add main application file

Verifying the Reset

Let's verify that the cherry-pick has been undone:

git log --oneline

You should see that the cherry-picked commit is no longer in the history:

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

Let's also check if the feature.js file has been removed:

ls -la

The output should not include 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

Congratulations! You've successfully undone a cherry-pick operation using git reset. This method is clean and simple, but it rewrites history, so it should only be used for local changes that haven't been pushed to a shared repository.

Undoing a Cherry-pick with Git Revert

In the previous step, we used git reset to undo a cherry-pick. However, git reset rewrites history, which can be problematic if you've already pushed your changes to a shared repository. In this step, we'll learn how to use git revert to safely undo a cherry-pick without rewriting history.

Understanding Git Revert

The git revert command creates a new commit that undoes the changes introduced by a previous commit. Unlike git reset, which removes commits from history, git revert adds a new commit that counteracts the changes, preserving the commit history.

Performing a Cherry-pick Again

First, let's cherry-pick the commit again so we have something to revert:

## 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

You should see output similar to:

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

Checking the Current Status

Let's confirm that the cherry-pick was successful:

git log --oneline -n 3
ls -la

You should see the cherry-picked commit in the history and the feature.js file in the directory listing.

Reverting the Cherry-pick

Now, let's use git revert to undo the cherry-pick while preserving history:

git revert HEAD --no-edit

The --no-edit flag tells Git to use the default commit message without opening an editor.

You should see output similar to:

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

Verifying the Revert

Let's check the commit history:

git log --oneline -n 4

You should see both the cherry-picked commit and the revert commit:

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

Now, let's check if the feature.js file has been removed:

ls -la

The output should not include 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

Although both git reset and git revert achieve the same end result (removing the changes introduced by the cherry-pick), they do so in different ways:

  • git reset removes the commit from history, which can cause problems if the commit has been shared with others.
  • git revert adds a new commit that undoes the changes, preserving the commit history, which is safer for shared repositories.

Choosing between these methods depends on your specific situation:

  • Use git reset for local changes that haven't been shared
  • Use git revert for changes that have been pushed to a shared repository

Handling Cherry-pick Conflicts

Sometimes when you cherry-pick a commit, Git might encounter conflicts if the changes in the commit conflict with changes in your current branch. In this step, we'll learn how to handle cherry-pick conflicts and how to abort a cherry-pick operation.

Creating a Scenario with Potential Conflicts

First, let's create a scenario that will result in a cherry-pick conflict:

## Switch to main branch and modify 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"

## Switch to feature branch and make a conflicting change to 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"

Attempting a Cherry-pick with Conflicts

Now, let's switch back to the main branch and try to cherry-pick the commit from the feature branch:

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

Since both branches modified the same lines in README.md, you should see a conflict:

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'

Viewing the Conflict

Let's examine the conflict:

git status

You should see output indicating a conflict in 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")

Let's look at the content of the conflicted file:

cat README.md

You should see something like:

## 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

Resolving the Conflict

To resolve the conflict, we need to edit the file and decide which changes to keep. Let's modify README.md to include both changes:

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

Now, let's mark the conflict as resolved and continue the cherry-pick:

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

Git will open an editor with a default commit message. Save and close the editor to complete the cherry-pick.

Aborting a Cherry-pick

Sometimes, you might decide you don't want to resolve the conflicts and would prefer to cancel the cherry-pick operation. Let's create another conflict and then abort the cherry-pick:

## Create another conflicting commit in feature branch
git checkout feature-branch
echo "// This will conflict with app.js in main" > app.js
git commit -am "Modify app.js in feature branch"

## Try to cherry-pick this commit to main
git checkout main
ANOTHER_CONFLICT=$(git log feature-branch --oneline | grep "Modify app.js" | cut -d ' ' -f 1)
git cherry-pick $ANOTHER_CONFLICT

You should see another conflict. This time, let's abort the cherry-pick:

git cherry-pick --abort

You should see that the cherry-pick operation has been cancelled, and your working directory has been restored to its previous state:

git status

Output:

On branch main
nothing to commit, working tree clean

Handling conflicts during cherry-pick operations is an essential skill for Git users. You have three options when you encounter a conflict:

  1. Resolve the conflict manually, then use git add and git cherry-pick --continue
  2. Skip the conflicting commit with git cherry-pick --skip
  3. Abort the entire cherry-pick operation with git cherry-pick --abort

The best approach depends on the specific situation and your project requirements.

Summary

In this lab, you've gained practical experience with Git's cherry-pick feature and learned multiple ways to undo cherry-pick operations:

  1. You created a test repository with multiple branches and commits to practice with
  2. You performed a cherry-pick operation to apply a specific commit from one branch to another
  3. You learned how to undo a cherry-pick using git reset, which is suitable for local changes
  4. You explored how to safely undo a cherry-pick using git revert, which preserves history
  5. You practiced handling cherry-pick conflicts and learned how to abort a cherry-pick operation

These skills are invaluable when working with Git in real-world projects. Cherry-picking allows you to selectively apply changes across branches, while knowing how to undo cherry-picks helps you recover from mistakes and maintain a clean Git history.

Remember that different undoing methods serve different purposes:

  • Use git reset for local changes that haven't been shared
  • Use git revert for changes that have been pushed to a shared repository
  • Use git cherry-pick --abort to cancel an in-progress cherry-pick operation

By understanding these options, you can choose the most appropriate method for your specific situation.