Cherry-Picking

We’ve now dealt with two ways to move code from one branch of a Git repository to another: merging and rebasing. Both merging and rebasing transfer all of the code currently on the source branch to the destination branch, which is not always desirable. To move only some of the source branch’s code to the destination branch, we use a procedure called cherry-picking.

No one found the bug from last week’s program, which doesn’t surprise me since it’s a subtle one. The bug is actually twofold, and both aspects have to do with how the Tkinter GUI toolkit handles modifier keys. The first aspect of the problem is that the keybinding Alt-c is only triggered when the focused window receives a lowercase c. If the user has Caps Lock on, pressing Alt and the C key will send a capital C, and the keybinding will not trigger. Open GitHub Desktop to the add-counter branch of the rcp-demo repository we were working in last week, and open the file helloWorldGUI.py in Visual Studio Code. Copy the line where we set up the Alt-c keybinding and put an otherwise-identical keybinding for Alt-C directly below it. Don’t commit the change yet; we still have another fix to apply.

The second aspect of the bug is that Tkinter interprets the keybinding Alt-c in such a way that it triggers when the focused window receives a lowercase c while the Alt key is down. The problem with that is that other modifier keys may be up or down: pressing Ctrl-Alt-C will also trigger the keybinding. The same problem occurs with the Return keybinding: pressing the Enter key with any combination of modifiers will trigger the keybinding. In order to ensure that an action takes place only when the specified modifiers and only those modifiers are down, we must test the state variable of the keypress event to see whether its value matches the set of modifiers we want to trigger on. The state values of interest to us are shown below:

Modifiers Value of state
None 8
Caps Lock only 10
Alt only 131080
Alt and Caps Lock 131082

Note that Tkinter treats Caps Lock as a modifier key; since we want our keybindings to function regardless of whether Caps Lock is on, each binding will have two possible states in which it could trigger. For clarity, we will declare these state value as constants at the top of the program.

Now, in each of our bind statements, we add a test of the state value to the action we want performed. So the original binding of the Enter key to close the popup box, popup.bind('<Return>', lambda e: closePopup.invoke()), becomes popup.bind('<Return>', lambda e: closePopup.invoke() if e.state in [NO_MODS, CAPS] else 0). The else 0 tells Python to do nothing if the state value is not either of the desired values. Each of the other keybindings would be modified similarly, as you can see below:

Now close VS Code, go back to GitHub Desktop, and commit the changes with the message “Fixed keybindings.”

Now we are in a quandary: how best to get the fix from our feature branch onto main. The feature we’re working on (a counter to show how many times the popup has been shown) isn’t done, so we shouldn’t merge the branches yet; at the same time, however, we shouldn’t make the bug fix wait until our feature is done to be brought into main. The solution is to cherry-pick the bugfix commit from our feature branch onto main. In GitHub Desktop, switch the left-hand sidebar to the “History” tab. Now drag the “Fixed keybindings” commit up to the “Current branch” dropdown at the top of the screen, and from there onto the word main in the dropdown. Let go of the mouse button, and the changes will be copied to the main branch.

Or at least they should have been. For some reason, Git is being stupid and thinks we’ve changed something on the main branch after rebasing the feature branch, and it can’t figure out what I want the cherry-pick to accomplish. We’ll go into more detail in the next post about when we would expect this to occur legitmately, but for now click “Open in Visual Studio Code.”

On opening VS Code, we find that Git has been even more stupid than I initially thought: for some reason, it’s trying to pull in the counter definition even though that wasn’t part of the commit I asked it to cherry-pick. Select and delete the contents of lines 4-5 and 10-13, then save and close VS Code. The file should look like this when you’re done with it:

Go back to GitHub Desktop and click “Continue cherry-pick.”

We can now see that the “Fixed keybindings” commit from our feature branch is now part of the commit history on the main branch.

So the lesson to be learned from today is that, while cherry-picking can be a useful way to move only some of the code from one branch to another, you have to be careful with it to make sure that Git actually does what you think it’s going to do. In this particular case, it wouldn’t have been the end of the world had the counter been silently cherry-picked along with the constant definitions, but in a larger application, cherry-picking a line you didn’t intend to could have unexpected consequences.

Comments

Popular posts from this blog

Our First Repository

Getting Started