In the buffer
Before:
v1.0.0 first public release
^
v1.1.0 add JSON output
v1.2.0 drop Python 3.8
v1.2.1 fix crash on empty input
v1.3.0 add --quiet flag
After typing :g/^/m0:
v1.3.0 add --quiet flag
^
v1.2.1 fix crash on empty input
v1.2.0 drop Python 3.8
v1.1.0 add JSON output
v1.0.0 first public release
The oldest release is now at the bottom, and the cursor is on line 1. To see why the lines come out reversed, follow the moves. :g marks all five lines first, then visits them from top to bottom. v1.0.0 moves to the top, which leaves it where it was. v1.1.0 moves to the top and lands above v1.0.0. v1.2.0 lands above that. Each line ends up on top of the ones moved before it, so the last line visited finishes first.
When you would reach for it
You have a list in the wrong direction. Maybe a changelog with the oldest entry first, log output you want to read newest first, or a stack of notes you appended to the bottom for a week. Sorting does not help, because the order you want is “the opposite of what is here,” not alphabetical. :g/^/m0 reverses the lines in place without a shell and without a plugin, and it does so in any vi you are likely to find on a server.
Gotchas
:sort!is not the same thing. It sorts in descending order, which matches a reversal only when the lines were already sorted. Onpear,apple,fig,:sort!givespear,fig,apple, while:g/^/m0givesfig,apple,pear. The changelog above is the trap: it was already in order, so both commands appear to agree.- If you add a range and keep
m0, the lines in that range get reversed and moved to the top of the file.:2,4g/^/m0on the buffer above puts lines 4, 3, 2 above line 1. To reverse a block in place, change the target to the line directly above the block::2,4g/^/m1. - One
uundoes the whole thing.:gcounts as a single change even though it ran five moves, so you do not have to pressuonce per line. ^marks every line, blank ones included, so blank separator lines get reversed with everything else. A paragraph structure of text, blank, text, blank comes back as blank, text, blank, text if the file ends with a blank line.
Variants
:2,4g/^/m1
Reverses only lines 2 through 4 and leaves them where they were. The target is always the line immediately above the first line in the range.
:%!tac
Same result, but it filters the buffer through the external tac command. Fast on huge files. It needs tac on your system, though: GNU coreutils has it, and stock macOS does not.
:g/^/t.
The same :g loop with t. (copy below the current line) in place of m0. Every line gets duplicated directly beneath itself, which shows how :g visits each line in turn.