-1

How can I directly edit git history as to "drag 'n drop" changes from one commit to another?

Within a branch I have three commits:

A--B--C

The contents of which are:

A -- add file_a
B -- add file_b
     mod file_a
C -- mod file_b

Currently I can manage to split the changes via interactive rebase:

A   -- add file_a
B'  -- mod file_a
B'' -- add file_b
C   -- mod file_b

The end result is then:

A   -- add file_a (changes from B' squashed into A)
B'' -- add file_b (changes from C squashed into B'')

I have been experimenting with git filter-branch (or git filter-repo which is recommended instead) in an attempt to accomplish this task but don't seem to be landing on the correct set of arguments.

It would be entirely acceptable to "drag 'n drop" file_a changes from commit B into commit A in one command and then squash commit C into commit B.

What I am looking to avoid is the manual work brought on by interactively rebasing. The act of going back in time to effectively redo the work in the desired steps feels too clunky (up to ~7 steps in command line from my approach, mileage may vary). A script would alleviate the number of commands needing to be executed (see Mikhail's posted answer to this question), but is what has been described possible with respect to directly editing the history?

There are arguments for better commit practices (or best practices in general) as to avoid this scenario in the first place but I would like to subvert these for the sake educational of pursuit.

  • 1
    If the changes are as trivial as creating 2 new commits (A-add file a, and B-add file b), then this is pretty easy: reset the branch, create commit A with the tree that includes file a, then create commit B with the tree that includes file B. Don't try manipulating the old commits at all. – William Pursell May 30 '23 at 14:27
  • 1
    I agree. This seems to me like a trivial case of Regret Type 2: https://stackoverflow.com/a/59675191/341994 – matt May 30 '23 at 14:31
  • @WilliamPursell I appreciate the solution proposed, this is one I have been taking advantage of via the interactive rebasing I mentioned. Pausing to edit commit B and splitting it brings me to the `A--B'--B''--C` situation. The example listed is trivial for the sake of simplifying the question, but more complicated use cases are a possibility. Having multiple repos in the same situation, for example. If I have 'n' repos, repeating the solution you and I are currently implementing 'n' times becomes rather tedious. Thus, I'm hoping to see if what I've described is possible at all. – Drewzillawood May 30 '23 at 15:18
  • @Drewzillawood I wonder if you've misunderstood what WilliamPursell and matt are proposing with `reset --mixed`? Note that if you have 100 commits that modify just 3 files, and you want just 3 commits- 1 per file, the reset will make it extremely easy to do that. With interactive rebase you still have to "iterate" over all 100 commits. – TTT May 30 '23 at 21:17

1 Answers1

0

I don't really understand what the problem is but I will try to tackl the case in point. You can do this:

git checkout A
git restore --worktree --staged --source=C -- file_a
git commit --amend -m "Final version of file_a, file_b does not exist over here"
git restore --worktree --staged --source=C -- file_b
git commit -m "Final version of file_b, no changes on file_a"
eftshift0
  • 26,375
  • 3
  • 36
  • 60
  • That's fair, I think the problem, to me, can be illustrated with other commands. `git cherry-pick ` I can specify a commit hash and grab a commit from anywhere, one-and-done. `git revert ` again, specify a commit hash and you're good to go, one-and-done. But when it gets down to a more granular level like a specific file we're basically left with chucking these commands out the window and told to "start over." Resetting back in history, manually arranging the new commits, and then specifying the commit messages feels like an infomercial "there has to be a better way" moment, IMO. – Drewzillawood May 30 '23 at 16:12
  • but why are you trying to cherry-pick/revert? (not to mention the nuclear option: `filter-repo`) You pick _tools_ depending on the _task_, it's not the other way around. You can pull it off (your specific scenario in the question) with restore, once you have the final look of the files on the last commit. – eftshift0 May 30 '23 at 16:16
  • The situation I am in where I want to take advantage of this type of behavior is after working through a particular issue and I have a long list of commits that aren't ideal as they stand. Prior to pushing my changes I often do an interactive rebase to reorganize. Let us say that the commits aren't atomic, for example, and I'd like them to be. The solutions described so far feel akin to "create a new branch, do all the changes again with atomic commits." I understand them, I use them all, I have executed them frequently. But in the end, it feels like a tedious task that doesn't have to be. – Drewzillawood May 30 '23 at 16:27