6

When refactoring I sometimes do too much at once—all little steps, but I forget to commit after each little change.

Of course in many cases a commit after each change would be too much, but if each individual change were initially made as a separate commit, it would be very easy to git rebase them to combine them into logical, atomic changes.

Is there a way to convert each point in Vim's multiple undo history into a separate git commit?

2
  • 2
    Why don't use git add -p? Using this command someone can selectively stage modifications; edit, split into smaller hunks and commit them.
    – dNitro
    Commented Sep 28, 2016 at 10:13
  • 2
    @dNitro, good answer for most people, but I'm already familiar with that. It's not quite so useful when you end up modifying every line of the file so the whole thing is considered as one "hunk." Of course you can remove lines by editing the hunk manually, but then there are the cases when you change a line and then change the same line again.
    – Wildcard
    Commented Sep 28, 2016 at 19:56

3 Answers 3

6

Here's a quick and dirty function that does what you request:

function! UndoCommits(steps)
    for i in range(a:steps)
        undo
    endfor

    for i in range(a:steps)
        write
        execute printf('!silent git commit -am "%s"', "undo-commit" . i)
        redo
    endfor
    write
    execute printf('!silent git commit -am "%s"', "final-undo-commit")
    redraw!
endfunction

Call with :call UndoCommits({N}) where {N} is the number of undo steps back you want to go.

The function simply undoes the requested number of times, and then redoes the changes one at a time, making a commit between each change.

Personally, I think I'd just do this manually — I guess a compromise might be to create a mapping that just performs the save-and-commit, and then run this mapping by hand at the relevant parts of the undolist.

4
  • UndoCommits doesn't incorporate branches in the undo history, though.
    – Geremia
    Commented Mar 30, 2022 at 18:19
  • Is there a way to time-stamp each commit with the date/time of the undo? (Gundo get the time of each undo somewhere; I'm not sure where in its source code.)
    – Geremia
    Commented Mar 30, 2022 at 19:55
  • 1
    @Geremia Continuing with the "quick-n-dirty" theme, you could get it to include all entries from the undo tree by using :earlier and :later instead of :undo and :redo. If you want to include the time information, you're going to need to implement something a bit more complicated that cross-references the return value of undotree(), which contains this information.
    – Rich
    Commented Apr 4, 2022 at 10:31
  • @Geremia I rejected your edit because it didn't preserve the original answer. Please post an answer of your own with you solution this way future readers can see the different options and choose the one they prefer.
    – statox
    Commented Apr 6, 2022 at 9:40
3

I would recommend one of the undo tree visualizer plugins:

With them, you can easily revisit previous editing states. You can them (temporarily) restore them (one by one, starting with the oldest relevant), and git commit them, until you arrive back at the original.

2

Building upon Rich's solution, this one timestamps each commit:

function! UndoCommits()
    let steps = undotree()['seq_last']
    for i in range(steps)
        earlier
    endfor
    for i in range(steps)
    write
        execute printf('!git commit -am "%s" --date=%d &>/dev/null', "undo-commit" . i, undotree()['time_cur'])
        later
    endfor
    write
    execute printf('!git commit -am "final-undo-commit" --date=%d &>/dev/null', undotree()['time_cur'])
    redraw!
endfunction

Usage: :call UndoCommits(). It will make git commits starting from the current position in the undo tree all the way back to the oldest revision.

9
  • Oh hey that didn’t turn out to be that much more complicated at all. Good job!
    – Rich
    Commented Apr 7, 2022 at 8:47
  • This only does a single branch of the tree though, right? To get the entire graph would be harder
    – D. Ben Knoble
    Commented Apr 7, 2022 at 15:42
  • @D.BenKnoble It makes Git commits with the whole undo tree from the current position in the tree. It does not make new Git branches to reflect undo tree branches.
    – Geremia
    Commented Apr 7, 2022 at 19:52
  • Agreed about git branches; what I’m looking at is that either it process the whole tree in some kind of linearized fashion, or it only process the linear history between start and now. So either way some information is lost, compared to plugins that show the entire undotree in a tree
    – D. Ben Knoble
    Commented Apr 7, 2022 at 21:28
  • @D.BenKnoble "either way some information is lost" No undo tree revisions (on only undo tree branch) between the current one and the file's first one are lost. "either it process the whole tree in some kind of linearized fashion, or it only process the linear history between start and now" What's the difference?
    – Geremia
    Commented Apr 8, 2022 at 2:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.