Hubro
Hubro

Reputation: 59343

(Git) What's the most practical way to commit a fix to a different branch when you have lots of changes in your current branch?

This is the scenario:

Then, you realize the last few changes you made doesn't belong in this feature branch. Instead, these changes should be committed to a different feature branch, a hotfix branch or even directly to develop.

Now, what is the most practical way to get your non-feature related changes committed to a different branch?


Elaboration

The "straight forward" way to handle this would be to stash your changes, check out the correct branch, commit your changes there, check out the feature branch again, pop the stash, resume working. (Optionally merge in the new branch.)

While simple in theory, this can be extremely impractical and tedious in many cases. First of all, the changes you want to move got stashed... So how exactly do you get them into the new branch? You could maybe pop the stash in the new branch? But if you are several commits deep into your feature branch, I wouldn't expect the current stash to be compatible with the develop branch.

I wish I could just commit the off-topic changes, then move that commit to a different branch with a single command, without leaving my current branch.

NB: I have seen other similar questions to this on SO, namely "how do I commit changes to a different branch without checking it out". The answers are pretty much always "you can't". But this is not what I'm asking here, I want to know the most practical way to get your changes moved to a different branch, when you're currently neck deep in a feature branch.

Upvotes: 1

Views: 432

Answers (4)

aij
aij

Reputation: 6491

I find it useful to use git worktree.

I'll assume a near worst-case scenario, where you are on a feature branch with a lot of changes committed, with some changes that have not been committed nor staged which belong on the feature branch, and that you've already started making some of the cleanup changes touching some of the same files as the unstaged feature changes. Also, the build system is slow and based on timestamps, so you want to avoid switching to a branch based off develop and back as that would ruin your day.

  1. Stash the cleanup changes: Run git stash -p and select the hunks that should be part of the cleanup commit. (You may want to commit files containing only feature changes first, in order to reduce the number of chunks to go through here.) If a hunk contains both feature and cleanup changes, you'll need to manually extricate them, so stash or not depending on where you want that hunk to go initially.

  2. Add a worktree (or recycle one): Run git worktree add -b cleanup/the-thing-to-fix ../name_for_new_directory develop to create a branch named cleanup/the-thing-to-fix based off of develop and checked out in a new worktree at ../name_for_new_directory.

  3. Switch to the worktree: Run cd ../name_for_new_directory

  4. Unstash changes from step 1: Run git stash pop. This works because we're still in the same git repo / clone as before, just on a separate branch checked out in a separate directory.

  5. Commit the cleanup changes as normal: Run git commit -a (or use whatever options you usually use to commit.

Now that the changes are separated, you can continue working on either one as you choose based on your specific circumstances.

Upvotes: 1

jthill
jthill

Reputation: 60313

  • While working on some code you didn't write, you notice and immediately fix a bug
  • Alternatively, you notice a function is missing documentation so you quickly add some pro tips for the next developer that comes along

In the usual case, where there's no mixing of changes (changes that should be committed separately are in separate files)

git checkout otherbranch
git commit -- files to change
git checkout -

because when you check out a different branch, changes in the index and work tree stay there (git won't overwrite changes you've made, see the next paragraph, but it's happy to let them stick around so long as they're not in the way), and when you specify a list of paths to git commit it bypasses the index and adds just those files to the current tip.

If you've made changes to files that otherbranch also changed, you can learn about merge checkouts, add the -m switch (also imho pronounced "magic"). Practice this with work you've git stash; git stash apply'd so you can easily git read-tree -um to get the contents back.

Upvotes: 0

torek
torek

Reputation: 488539

My preferred method is to start by committing now, on the branch you're on. Sometimes this is optional, depending on the remaining steps, so read through them first.

Now that you've committed everything or not, the next step depends on whether it's OK to mess with the current index and work-tree. If not, and you have Git 2.5 or later—preferably 2.15 or later because of a fairly nasty bug first fixed in 2.15—use git worktree add to create a new work-tree that is set up to work on/in the branch where you want to make these fixes:

# this assumes you're in the top level of your repository
git worktree add ../quick-fix develop
cd ../quick-fix

Here ../quick-fix is the new work-tree you'll create, which is on branch develop. By doing cd ../quick-fix—or opening a new Terminal window on your Mac, perhaps, and going into the directory there—you're now working in your main repository, but on the develop branch, not the feature branch.

At this point, because you already committed in your feature branch, you have access to all of the commits you have made in your feature branch and can use git cherry-pick, or git show | git apply, or whatever you like—any of Git's tools or any of your system's tools—to work in the develop branch. If you didn't commit, you can't cherry-pick that last commit, of course.

When you're done updating your develop branch, you can return to your feature branch work-tree, run rm -rf ../quick-fix to remove the other work-tree, and run git worktree prune to get git worktree to realize that the other work-tree is now gone. (It's safe to remove once you've made all the commits you intend to make, but you do have to run git worktree prune at some point to remind Git that you don't have develop checked out any more.)

If you cannot or do not wish to use git worktree add, you'll have to work in your main work-tree, so you definitely need to have done that git commit. You can now git checkout develop here, work as usual, and commit as usual. Once you're done, git checkout feature again to get back to your feature.

Either way, you are now back to one work-tree, which has feature checked out. It's now time to remove the commit you made on feature, assuming you made one on feature. To remove that one commit, run:

git reset HEAD^

(or use HEAD~ instead of HEAD^ if that's easier for you to type and/or better in your shell / CLI, e.g., if ^ has special meaning to your shell). This removes the commit and resets the index, but keeps the work-tree from the temporary commit you made.

Key points

We make a temporary commit if necessary. It is necessary if:

  • you want to cherry-pick from it, or
  • you want to re-use your main work-tree and its corresponding index

Otherwise, it is not necessary.

We use git worktree add to create a new work-tree/index pair that allows you to do work in another branch, without having to disturb the main work-tree and Git's main index.

We use git reset in its default (--mixed) mode to erase the temporary commit once we are done with it. This also resets the index, so if you are using the index for special tricks—sparse checkout, or git update-index with --assume-unchanged or --skip-worktree—be at least a little bit careful here (but git reset does coördinate with these).

Upvotes: 1

GoodDeeds
GoodDeeds

Reputation: 8527

You could do the following:

  1. Add and commit only the changes you want to using git add --interactive (or more specifically, git add --patch). More details here.
    git add --patch
    git commit
  1. Stash other changes, go to develop branch.
    git stash
    git checkout develop
  1. Apply only the commit from step 1. to develop from feature.
    git cherry-pick <commit-id-from-step-1-in-feature>
  1. Go back to feature branch and restore other changes.
    git checkout feature
    git stash pop

Upvotes: 1

Related Questions