gitgit-submodulesgit-rebase

Git rebase over a commit that extracts files into submodule


I've got a git history like so:

commit 027dbda16679e8c737c8ec63676aa564807cfaeb (HEAD -> master)
Author: Camden Narzt <c.narzt@me.com>
Date:   Thu Dec 19 15:36:42 2024 -0700

    migrate homebrew stuff into tap
    update setup script for new tap

commit 53d1c2147f78144098c3fc7c62f6a6f6e00063e7
Author: Camden Narzt <c.narzt@me.com>
Date:   Wed Nov 20 23:24:06 2024 -0700

    don’t push

commit 3fbb7f8af3870237627ae7d0e027697499ffa74b
Author: Camden Narzt <c.narzt@me.com>
Date:   Wed Dec 18 12:42:15 2024 -0700

    bump colima config

commit 53d1c2147f78144098c3fc7c62f6a6f6e00063e7 contains stuff I don't want to push, but which I put in a commit in order to be able to work with the history more easily (yes, I should have stashed the changes instead, my bad). I do not want to lose the changes in 53d1c2147f78144098c3fc7c62f6a6f6e00063e7, but do not want to keep them in the position they are in, in the history, as I'd like to push everything else.

When I run: git rebase -i 3fbb7f8a and move commit 53d1c2147f78144098c3fc7c62f6a6f6e00063e7 to the end so that it is the latest commit, I get the following error from git:

error: The following untracked working tree files would be overwritten by checkout:
    homebrew/Brewfile
    homebrew/Brewfile.lock.json
    homebrew/emacs-test.el
Please move or remove them before you switch branches.
Aborting
error: could not detach HEAD

The files mentioned in the error message were deleted, and then replaced with the contents of the submodule in commit 027dbda16679e8c737c8ec63676aa564807cfaeb. I would like to use the files from the submodule going forward. Strangely, not all files that were moved into the submodule are mentioned in the error.

How can I move commit 53d1c2147f78144098c3fc7c62f6a6f6e00063e7 to the end, so that I can push the other commits to the remote?

Commit 53d1c2147f78144098c3fc7c62f6a6f6e00063e7 does not involve the files mentioned in the error at all.

Here's the files from the commit that introduced the submodule:

.gitmodules
Formula/awscli.rb
Formula/bdsup2sub.rb
Formula/fakeapxs.rb
Formula/libipt.rb
Formula/libxed.rb
Formula/mbuild.rb
Formula/pam-duress.rb
Formula/tiny_php.rb
Formula/vobsub2srt.rb
homebrew
homebrew/Brewfile
homebrew/Brewfile.lock.json
homebrew/Formula
homebrew/bin
homebrew/emacs-test.el
setup.sh
usr/local/bin/brew-baggage
usr/local/bin/brew-bottle-outdated
usr/local/bin/brew-clean
usr/local/bin/brew-junk

setup.sh was modified, .gitmodules was added, homebrew was a directory that was replaced with the submodule, all others were deleted.


Solution

  • (yes, I should have stashed the changes instead, my bad)

    I strongly disagree, you're doing good! Stash is bad and you should absolutely not use that. Instead continue checking in this as temporary commits like you already do.

    This is the proper way of handling your "don't push" changes, you are only bit by a conflict due to the introduction of a submodule, and this problem would occur for any interactive rebase when moving a commit from before commit 027dbda1 to after commit 027dbda1.


    My handling would be:

    git branch move_prep 53d1c2
    git switch move_prep
    git rm homebrew/Brewfile homebrew/Brewfile.lock.json homebrew/emacs-test.el
    git commit -m "-- remove homebrew files --"
    git revert HEAD
    git diff move_prep 53d1c2     # Will show no changes
    

    Now you want to rebase your master branch containing commit 027dbda166 to be on top of branch move_prep. Since that branch is identical with commit 53d1c2 you will encounter no conflicts.

    git branch master.backup master
    git rebase move_prep master
    

    Now you start the first interactive rebase and change the line with "-- remove homebrew files --" to fixup so that it joins the "don't push" commit to a new commit that does not contain the files that are moves to the submodule, and thus this new commit now can be moved to after the submodule is introduced without any problems.

    # On master branch
    git rebase -i 3fbb7f8af
    
    pick 53d1c21 don’t push
    fixup 1111111 -- remove homebrew files --
    pick 2222222 Revert commit 1111111
    pick 3333333 migrate homebrew stuff into tap
    

    With this done we have a new 4444444 "don’t push" commit that can be moved without problems (and the corresponding revert should be dropped). So start the second interactive rebase

    git rebase -i 3fbb7f8af
    

    and change from

    pick 4444444 don’t push
    pick 2222222 Revert commit 1111111
    pick 3333333 migrate homebrew stuff into tap
    

    to

    pick 3333333 migrate homebrew stuff into tap
    pick 4444444 don’t push
    

    and now your master branch should be like you wanted (and you can delete the move_prep and master.backup branches).

    git diff master.backup master    # Should show no difference
    

    All the above depends on the commit moved not changing any of the files that live in the submodule. If any such files were modified those changes would be lost (in the outer repository). To keep them I think I would have attempted a combination of git format-patch (on commits where the homebrew changes has been extracted as the only changes), edit file names in the patch files and then git am inside the submodule.