When I looked, it had the same problem that Dropbox has with this: a textual listing with only timestamps. I need semantic info! Show me the differences. Don't make me choose from this arbitrary list. I want to scan through the versions very, very quickly.
Not sure what you're looking at, but IntelliJ 2020.3 gives me a list w/ timestamps in 1/3 of the modal, then a diff viewer in the other 2/3. I can down-arrow through the versions and the diff updates.
Personally, when I'm ready to submit a pull request, I usually do a soft reset to the point where I branched off from, then redo my commits and force push to the branch, for exactly that reason. I can't remember exactly which lines I've changed over the past few days unless I see all the diffs together.
I use view local history of selected so I can blame myself and view git history of selected to I secretly seethe at some one who left the company 3 years ago.
Yep, it's quite a handy feature at times. I don't use it a ton, but it is very helpful when I want it. One very nice aspect is that you can scope it to a specific section within a file (the same can be done with git history as well).
Vim has time-based undo which is great, but I always forget about it and need to look up how to use it the few times that I want it.
Having a slider/timeline does sound like an excellent idea. Also just being able to non-destructively go back to a previous state and have a peak without changing the files I'm working on right now would would really well for me I think.
Of course git is an answer to this, but I don't want to think about making a checkpoint.
In Vim you can achieve something quite similar with gundo plugin. Vim tracks all your changes in a tree by default and you can also use the "earlier" and "later" commands with a time delta to go back and forward a specified amount of time. So in Vim, to see the state of the file 5 minutes ago, you type ":earlier 5m" . Gundo is a visualisation on top of the builtin undo. I'm sure Emacs has something similar and probably more powerful, but I'm not familiar with it. I'm not aware of other editors having tree undo.
Emacs does indeed have a plugin for this called "undo-tree". I think it is missing the ability to specify a time to go back by, but you can view the timestamps in the tree visualization.
For anyone that uses emacs, there is an excellent plugin "undo-tree" that solves this problem in a slightly different way. Everytime you undo a series of actions and then perform a new action, it creates a branch in the "tree". You can then visualize the tree and move through it quickly your entire buffer history.
This was my first thought. A timeline is nice, but the problem with a linear undo history is that if you undo and then edit, you lose access to the state before you undid. A tree is clearly the right structure for undo history.
Heck it's how boring business apps handle bookkeeping data since forever. You never delete a record to correct a mistake, you create a new record to do it.
The tree structure is inherently flawed for storing undos when using features like undoing within a specific region, because the new state doesn't map to any previous node on the tree.
By default, Emacs has a linear undo structure, while still allowing you to never lose history.
On this point, C-h m yields a complete list of keybindings for all modes active in whatever buffer you do it in.
More generally, for anyone new to Emacs and struggling as I once did, I can't recommend strongly enough that you learn how to use the help system (C-h C-h) and the built-in manual (M-x info). Emacs can teach you a great deal about itself, and these are the ways it does so.
That concern is isolated to your region selection tool, and orthogonal to the undo functionality :-) So, you could select by lines, or intelligently select by parentheses/braces, etc. And ask these are nicely composable, so you could easily set up custom shortcuts if you don’t like the sequence of keystrokes.
Came here to post this. The undo tree is actually built into Vim, but I didn't know about it until I discovered Gundo a decade or so ago. I rarely use it, but it's still installed and has saved me hours of work on a few occasions.
And if you use IntelliJ/Android Studio there's Local History, which shows your git revisions but also periodic versions of your code outside of your commits
Even without undo-tree, Emacs undo history is "persistent". By that I mean that changes aren't overwritten, so the statement in the article that undoing stuff and then making edits loses the undone changes is not true for Emacs. You just have to undo back through the new edits, then you will start to redo the old undone edits, and so on. It can be tedious, but just the knowledge that it's all there is quite liberating.
Another vanilla feature I sometimes use to keep track of old code is the "kill ring". Just cut the old code and it will be available in there later if needed.
This this this. It was the first thing I missed in Vim when I switched. You also don't lose in Vim, as it keeps it the history as a tree, but it is harder to navigate the tree than Emacs' linear history.
If I remember correctly, Eclipse used to have (still has I guess) a feature were it would store code changes in a local history independent from your version control.
Not exactly the same, but as a voice actor the DAW is my "IDE", and configuring it save a time-stamped backup of the project file every five minutes has really saved my bacon several times, especially when the undo tree won't let me undo back to the state of 5 minutes ago.
I've gotten in the habbit of just commiting very frequently. I have a git alias "git cam" that just commits everything with the message "nt". Then once I've got something working, I use GitUp (which makes it very easy to merge/split/rearrange commits) to collapse all the nt commits into one or two coherent "real" commits.
> Why is it so hard to see code from 5 minutes ago while in the middle of a change?
In vim you can do:
:earlier 5m
to see the code you had 5 minutes ago.
> (1) If you go to a prior state and then make a new change, you can no longer redo and all those changes are lost.
Vim makes a tree, so that doesn't happen with it. When you undo and make a new change, you're just making a new branch. u/Ctrl-r goes from leaf to trunk, but with g-/g+ or :earlier/:later you can walk the whole tree.
Emacs similarly doesn't lose changes in this scenario, but instead of making a tree, it has a ring and undo is an action that can be undone.
> (2) You can not see a side-by-side comparison of the previous version and the latest version.
It's probably not complicated to make a macro or function where you go to a previous version, copy the buffer, paste it in a new one on a new window, return the original buffer to the state you were in, and diff the windows for that side-by-side comparison.
A command like so suffices:
:earlier 5m | %y | later 5m | diffthis | vnew | put | 1d | diffthis
> (3) There is no visual indicator of where you are are in your undo/redo history.
Not by default, no. :undolist provides some info, though, and the vimscript undotree() function probably provides all the state of the undotree. There might be plugins that somehow present this info in the interface.
> (5) I have found many actions in code editors that do not get added to the undo stack (e.g., changing a debugger option), which caused me problems in the midst of an annoying bug.
In vim, only stuff that changes the buffer is added to the undo tree. Navigation or changes of the state of the editor (e.g. editor options or vimscript variables) aren't added. Are there really editors where adding actions that aren't changes make sense?
> (6) There is no indication of what steps were "big" or how long ago they happened.
That's also true in vim it seems, at least how "big" they where. Each step does have a timestamp, though.
> (6) It is tedious to backtrack one small step at a time.
You can backtrack by however many steps you want. :earlier also supports specifying by seconds, minutes, hours, days, or file writes.
Most cool of all is that vim can persist the undo history. I don't know how common that is among editors, but in vim you can go to a file you haven't opened in years and undo it all the way to its beginning.
This is excellent... for Vim users. A lot of developers either are not willing or haven't gotten around to learning the complexities of Vim, and use other IDEs like JetBrains or Eclipse. This workflow won't work for them.
JetBrains' various apps can do this as well with a feature called "Show Local History". I'm not sure where it is in the menus but you can hit shift twice and type it in.
It automatically commits after every change since the IDE was opened, and can easily be diffed and reverted as needed. Reverting saves another entry, so you can revert back to the future as well.
I am one of those "not willing" people. I encounter the same issue discussed here without even thinking about it, and solve it as such:
1. Realize I need to check how my new code looked like 5 min back.
2. Copy full file into clipboard, undo by a few steps
3. Do a temp commit on GitHub desktop
4. Paste the new code back and look at the diff on GitHub desktop, and if needed undo the temp commit from above.
Is this more clicks than the vin shortcut? Sure. But I don't have to go learn the internal tree structure representation of my text editor and spend my time in that universe. I have found a way to do what I need to with the tools I have in hand.
This is the same way how the majority of finance runs on excel when they could do better with better programing. In the end whether you get the job done in fields where you're not billed by the second is not dependent on how well you use your tools necessarily.
The other analogy is cars - for most people it's something that gets them places, for others it's a way of life if not at least a more involved proposition.
> The other analogy is cars - for most people it's something that gets them places, for others it's a way of life if not at least a more involved proposition.
But if code is your way of life, which it is for many people here, it is beneficial to learn the tools that allow you to do it easier. If someone can see how their code was 5 minutes ago in a few keystrokes, they’re going to take advantage of that significantly more frequently than someone using your cumbersome method.
It's not as hard to learn as people say it is. It's just weird at first because it's modal and different than what they're used to. You can get away with using it pretty naively and still get a productivity gain.
As some other comments suggest, vim is the gift that keeps on giving. I'm not going to try and convince you to use it -- do whatever you want.
But for me, every little thing I learn in vim pays off... a thousand times? Macros alone have probably saved me a cumulative few hundred hours. And with every other little thing I learn, compounds with this effect. I write macros much better than I did five years ago, but I still surely have tons of stuff to learn. No IDE will ever compete because vim is just four keystrokes away at any time, no matter where my terminal is.
Not so sure about this, I think it depends on how frequently you have to do that task. If you're doing it every day or even every week you could potentially be saving a huge amount of time and effort by using a tool that does the job well. It's sort of like using a random heavy object as a hammer, sure it's fine in a pinch if you need to occasionally hang a picture but if you're working as a carpenter it's counter productive. Comparing it to the notorious abuse of excel spreadsheets in finance is not doing the argument any favours.
If you learn Vim like anything else - slowly and starting with
the basics - it's not more complicated than figuring out any
IDE. And once you know the basics you are fully in control how
deep you want to go.
Personally I have found Vim more approachable than IDEs
actually. It doesn't throw all those overloaded toolbars at you
according to some one-size-fits-all principle. For me it went so
far that last year I actually did a Java project in Vim instead
of an IDE for the first time, because the latter had grown to be
so frustrating to use.
If writing text more than 4 hours / day is what you do for a living, and you have at least 10 more years left of doing that, not learning Vim is just lazy. Vim gives you back enough time to think about what you're writing.
Also, if you go cold turkey, you'll be productive in less than a month, and arrive at a good workflow that fits you in less than six months, flow that you will improve over the years in amazing ways.
And there are a lot of people, like myself, who do use vim regularly and somehow completely missed this feature. For those people the OPs post is excellent.
Likewise, if you have any recommendations for your preferred workflow I'm sure some will find it useful. Hopefully those who don't use your preferred $EDITOR don't then rush to complain that not everyone uses $EDITOR. ;)
I was going to mention this plugin. I haven't fully mastered quickly jumping around in it, but it's one of those things that when it's useful, it's VERY useful.
Ditto. Unless I have to go back a lot of edits, I don't actually use the plugin that often. I've mapped my undo/redo keys to traverse the undo tree instead of the undo stack:
nnoremap u g-
nnoremap <C-r> g+
It takes a little bit of getting use to, but it takes care of most instance where I need a previous edit from a minute ago.
> Are there really editors where adding actions that aren't changes make sense?
Adobe Photoshop is one. Selection changes don't modify the document at all, but they are part of the undo stack. That's because selecting a part of an image isn't as trivial as selecting text.
Sometimes I wish the current selection could be automatically saved in the file too so you could start where you left off if you were halfway through making a complicated selection (I know you can manually save selections, but that's an added step).
What is bad about it? It always goes back to the previous state, and if you want to skip previous undo states (so you don't undo an undo) you can use `M-x undo-only`.
It's been over a decade since my bad experience so take my words with a grain of salt. Iirc, the issues I had was that I made a huge history of undos-followed-by-redos-followed by-undos-etc etc and the more I navigated my history the more tangled and out of control it became. I think I also had a problem with often accidentally switching from undo to redo mode. Emacs documentation recommends using C-f (forward-character) to switch to redo mode, but in my opinion attaching side effect to movement is awful.
I agree. It's cool that it's lossless, but doing a lot of undos and redos makes the history quickly unwieldy to navigate. I much prefer vim's tree implementation.
I wouldn't say that it's worse than the standard, though. Unwieldy as it may be, I like that I can just keep on pressing C-/ and eventually get to the state I want without risk that I may have lost it because I accidentally made a change after a series of undos.
> Emacs documentation recommends using C-f (forward-character) to switch to redo mode, but in my opinion attaching side effect to movement is awful.
I use C-g (keyboard-quit) for that, which is the general let's-not-do-this-anymore keybinding. I think the documentation mentions C-f as an example keybinding that does the needful without much thought on being the "best" keybinding for it.
(undo-redo) doesn't seem to be defined in stock Emacs, but undo-tree defines (undo-tree-redo), which does the needful.
My comment was more about lamenting the defaults in comparison with vim's, but it's true that it doesn't take much configuration to make it much better.
undo-only doesn't have a keybind which makes it inconvient to use many times in a row. You could define one yourself I suppose, but at that point why not just get undo-tree?
> Most cool of all is that vim can persist the undo history. I don't know how common that is among editors, but in vim you can go to a file you haven't opened in years and undo it all the way to its beginning.
I absolutely love this feature. I regularly open a file and undo/redo to figure out where I was editing last.
NetBeans also have this but it's set to 7 days as default. Never looked into this before, just thought it was broken but you can save it for eternity if you want :-)
What particular feature? :earlier 5m? I'm afraid not. Emacs' C-/ acts like :earlier/:later/g-/g+ in that you can navigate the history across undos, but there's no support for specifying by number of minutes. I don't know if there's a package for it though. Evil-mode (vim emulation in emacs) doesn't seem to have support for that either.
I can't imagine it'd be that complicated to add it though, if you really want it. I think it'd just be a matter of hooking to the undo function and maintaining a list of timestamps for each item added to the undo list, then adding a command that lets you specify the number of minutes and figures out the correct undo item it should go back to by using that list of timestamps.
I'm quickly reminded of Photoshop's "History" window and how it was one of my first adds to the standard environment setup to make it visually prominent in the top right of the workspace. You could quickly click up/down it and view the steps, but the functionality is in the end the same as undo/redo, where you lose the others once you make a change after walking backwards.
I can't imagine it would be too crazy for IDE's to begin to add the same, per file at first but maybe also per project and then with saving some of those trees.
Pycharm has a local file history that even allows showing history for a selection. I'm not sure what triggers the periodic records, but it has saved me multiple times.
Does anyone know if there’s a good way to do this in VS Code? I often hold cmd-z until I get to some version and copy code from it. Ideally I’d like to have a separate window where I can see a previous version without affecting the current file/view.
Before anyone comments, git doesn’t cut it for this because no one commits after every -+1
Like an ongoing stream of history where I have a slider I can drag to revert on-the-fly.. That'd be cool. I mean you could easily record and do this, but that doesn't copy-paste well.
EDIT: And of course I read the article after reading the comments.. Haha, VS Code plug-in in 3.. 2..
If I have to commit my whole undo history then I’m using git very wrong. I commit (even temporarily) to save progress, but only when it makes sense to so. Sometimes I don’t know whether I’ll need this specific version again in the future.
But if there are more people in the team, it will be a great amount of noise they will have to go through before reaching any useful commit. Definitely not recommended.
You can stage/unstage selected ranges with vscode+git, useful but not a tree. Has bad behavior if you choose some portion of a range, or if the unstage will stomp on some other inflight change
I use it with every project and it works great. Helped me out a bunch of times. Just need to remember to ignore the .history folder from git and other tools.
Don't know if it serves any other purpose than coloring, but if you want to just go back to the last (and next) edit locations, Visual Studio already has a shortcut for that (Ctrl+Shift+'-' / Ctrl+'-')
One thing I think would help this is a dual repo setup with the local dev-only repo automatically committing on save (or if your watch is expensive committing unsaved editor state on pause). The repo could be short lived if you prefer (eg new per baseline repo branch or editing session) or a long lived series of stashes. But its history would be built into the editing experience and separate from the normal version control flow. I don’t think it would necessarily work for everyone but it could certainly fit how I work in exploratory mode without breaking my editing flow.
I remember using eclipse and a plugin for cfml years ago. If I help undo for a couple seconds to see old version and then redo for a couple seconds there was a 50/50 chance it would corrupt the code. If you haven't saved your latest changes or accidentally saving the corrupt code and have to compare to repo it gets old quick. Switched to Sublime.
If looking at a source browser of a previous version of your code is not adequate to understanding how it worked, such that you wish to "rewind" to that version and run it, then your code is very poorly written.
It is indeed difficult sometimes to understand our own code some time after it is written, but by following good practices (ideally functional, with mostly pure functions), it should be readable.
I will venture a (biased) guess that one of the biggest challenges to understanding the code was the mutation of objects in the process. This is the #1 reason why functional programming is superior. With FP, you can grok what a function does and then comfortably forget about how it does it. It takes input, it produces output. Period. As long as you agree with the transformation algorithm, you can reduce that complexity to a one-line description.
Even in non-FP-first languages, you can usually write pure functions. Some scenarios have enough performance demands that you must mutate things, but probably most situations can be approached with FP styling.
All this rewinding and undoing is a distraction from the real problem. The real problem is that "it smells". It smells like a level of complexity that should not be.
In my 30 years of professional programming, and even with my admittedly declining short term memory, I have never felt lost without access to versions of code from 5-15 minutes ago.
Not to be harsh, but I really think that this indicates some fundamental approach issue rather than a tooling problem.
If it cannot be written on paper (or tablet note with pen) as rough pseudocode... or even drawing boxes and arrows for the visually-inclined, then the problem is not understood. Take what is understood and codify it, pushing the complexity and the unknowns to the edges. At least that reduces the complexity.
One aid I do lean on heavily though is a REPL. Load the code and do some interactive work with it. Inspect the data, test some transformations, and then write code to do what works. That's my current approach to situations that I cannot immediately and accurately write in one pass.
> One aid I do lean on heavily though is a REPL. Load the code and do some interactive work with it. Inspect the data, test some transformations, and then write code to do what works. That's my current approach to situations that I cannot immediately and accurately write in one pass.
Honestly, this sounds mostly like what you're chastising, except you're doing it outside of the main workflow.
(Ok, looking at your profile and your background, I must be missing something. You surely don't need 5 or 10 minute history of code to know what you are doing.) Please help me understand what I am missing from this conversation!
You know what? Most of these ‘disagreements’ about coding workflow and tool choice in which one person is telling another person that they’re Doing It Wrong boil down to different strokes for different folks.
This seems to be a fine example of that.
(Though I must admit that consternation about how often someone presses Ctrl-Z is a bit lower level than I’m used to seeing.)
A common example from my work: I’m doing layout code, so I need to clean up some constraints that are broken. Often this a fairly manual process where I change some code and run it to see how it does, and then adjust the code as needed. Sometimes I need to change my strategy (instead of manually pinning this view, I can put it in a stack view…) and this requires me to change a couple lines of code. Then I run it and it breaks in a way that makes me realize that this direction was a dead end, so now I want to go back to where I was five minutes ago. Or, sometimes this is the way I wanted to go but in the process of rewriting it I forgot to carry over a constraint, so I want to see how I was doing it before and pull out a line or two so I can include it in the new code.
My short term memory is much reduced compared to my younger self. Consequently, I work in ways which allow me to reduce cognitive load and still accomplish the task. FP is a godsend for me, even if I just adopt some of its principles.
Likewise. I have ADHD and even with treatment, I feel more like a GPU than CPU. Small functions, lots of static typing, IDE for autocomplete, and lots of FP style patterns.
When I get thrown into an un-typed python codebase, my productivity grinds to a crawl, and I start having to do things like have a scratch pad with various versions to copy paste.
I learned very early in to "Commit early, commit often!"
That being said, I don't think the reasons viven are honest:
> When asked why they did this, they revealed they were trying to view some intermediate state of the code in the middle of a change.
Looking at some GitHub repos, with one single commit, I believe many devs are afraid someone could see all their trial and error attempts and judge them.
I oersonally am too lazy for the undo redo nonsense. Many of my final commits are mostly cleaning up the code. E.g. Refactoring so my code is like reading a story.
I don't have an issue with people thinking I have OCD or something similar. My bigger issue is if I ever have to touch my code again, to waste time on trying to understand it. Or even worse, someone else not understanding it and asking me for help.
> I believe many devs are afraid someone could see all their trial and error attempts and judge them
This is certainly part of the reason I do it. Sometimes I'll start repos anew, without the "mistake" commits and branches, and add that version to my Github before I send the link to a potential employer.
The closest imaginable thing I might do is wrapping some block of code in an if(false) {..} or #ifdef it out, when I'm refactoring and feeling unsure about my changes. But is that even the same thing that we're talking about here?
Hello from the bizarro universe of people who do this all the time and can't imagine the opposite. Do you never rewrite some code only to realize it didn't precisely capture all the edge cases of the thing you rewrote? In those cases I use the git gutter in VSCode to view what the old code looked like and compare and contrast to the new version to see what I missed. It seems to me that that's the same as what these engineers are doing, just with a bit more primitive tooling.
Can't imagine how anyone doesn't do this - unless there's just a race of God-programmers I've never encountered that write everything perfect the first go. But I don't think so. :-)
Yeah, sure it happens. Then I type “git diff”. But never have I unraveled the entire undo buffer stack only to peek at it and re-push the entire contents. That’s super weird to me.
What’s weird about it? For some - to do git diff might take more keystrokes than hitting undo 5 times.
I do all of these. I’d use undo/redo when I want to look at something I /just/ did, meaning while in the process of writing something. The git gutter when I stumble upon some change I want to see what was there before. Or Sourcetree for a full diff.
If there’s a specific implementation that’s especially hairy, I’ll just copy that block and comment it out before continuing. Instant reference. And if I’m rewriting more than 10 or so lines at a time of complex business logic, something is wrong.
Thought the same, copy-pasting valuable blocks as a reference into a comment worked fine. If it exceeds a one block rewrite, make small dirty commits to keep track of things.
On one hand it's nice that there are tools to support devs who get lost in their undo-redo history, on the other I feel like it's a matter of good habits to not even have this problem.
of course changing habits is hard, so maybe tooling is justified in this case. I'm just happy I don't have nother "history" type mental model to deal with.
Exactly. Sometimes you start heading down a change path, and as you discover more, you realize there is a better solution. You want to save some of your concepts, but discard others. That's what this helps solve.
Git stash can be helpful. Stash your "wrong direction" change, then apply just the good chunks from your stash.
TDD (test driven development) really help in solidifying interfaces ahead of time and also to capture regressions.
Yes, git diffs help, but once I’ve passed a parameterized suite of unit tests, never needed to undo/redo. Maybe not perfect the first go, but only need one/two tries to working code. Then refactor for readability.
TDD is definitely helpful, but I don't ever use it as a metric for code fitness. At best it proves a limited number of inputs produce a limited number of expected outputs.
Consider, for example, a bug recently introduced during a refactor at my work:
The programmer optimized a conditional based on a regex by transforming it into a simple string compare. All the tests passed, code/branch coverage was good. Except that he missed that the regular expression tested case-insensitively, and our test suite didn't test upper and lower case scenarios.
This simple mistake outlines a few flaws:
- coverage was not robust, because it didn't take into consideration the branches inside the regex (which one could interpret as a form of macro expansion)
- the limited number of inputs used to test failed to capture the broader domain of possible input
- the test did not reflect a successful refactor
Obviously this is a fairly complicated example despite a pretty simple change, and several pieces had to fail in order for this change to fail. But it affirms that even basic changes to code aren't necessarily adequately covered by TDD.
Yes, the developer who forgot to test different letter cases made a mistake. Yes, regular expressions bring their own problems. But fundamentally, the result was that passing the tests did not affirm fitness of the change. Rather, it only proved a limited subset of conditions were error-free.
Effectively, tests are loaded with false negatives, so trusting them to identify problems should be done with a massive grain of salt.
If the developer had simply copy/pasted the code he changed and compared the two, then it's exceedingly more likely that he would have noticed that his code didn't capture the full breadth of conditions in the previous code.
A bug existed in the test suite, to start, and then a bug was introduced into the codebase. A human being looking at the two lines of code as it was rewritten probably would have noticed the regression. But even a code review missed it because it was a fundamentally small change among the other, more "make sure this looks good" code.
Personally, I very much value copy/paste/compare changes and never treat the test suite for anything other than "well we haven't broken anything in any exceedingly obvious ways." Maybe you're a superhuman programmer, but I'd lean more towards "you've probably added more bugs than you realize".
There’s def. ways to write really good tests as well to avoid the limited input issue - write the test in a property-based test manner, like haskell’s quickcheck, and the test can catch entire classes of bugs.
The caps vs. no caps issue would be easily caught using randomString() as the test case.
you cant do it on a first go. i write a draft of what has to happen. often i draw drafts of the solution on a4 sheets of paper(my own blackboard, the best tool i know to make software). i do research and update the drafts to a point where i type in the code. code is the last thing i do. i laught that i make coloring books and not coding :) i am quite fast and i have little problems with switching contexts. the code is much better as i "see" the whole solution. i have much better abstractions. in a recent task i have found with this way of coding, many places with bad code and made them better as they didnt fit the cleanliness of the soulution. they just stand out like the eifel tower in paris.
It’s amazing how many developers don’t have a working knowledge of fundamental tools - like version control and debuggers. I honestly don’t know how you could get hired without this basic knowledge.
Undo/redo have nothing to do with this though. Undo/redo is for looking at changes you did while in the middle of writing something. Before committing.
This is from an example I did just yesterday.
Let’s say you’re moving a piece of code by cut n paste. You cut it but in the middle of changing you copied something else, maybe accidentally. Undo and cut it again. Redo and it’s now back in your clipboard.
You could copy from the diff but you’d get extra tokens to clean up.
I too am struggling to understand this use of undo.
If I'm reading this correctly it means applying a large number of undos to get to a previous editor state, followed by an equally large number of redos to then get back to where you started.
I can't recall every doing something like this.
The only time I seem to use undo is when I genuinely make some sort of editing mistake.
One reason I know I don't subconsciously do this is because I never struggle to execute an undo action.
However, on the rare occasions I need to do a redo I tend to struggle with the short cut key and usually go to the menu instead.
I am more stunned by the tone your response. Why are you implying there's a "struggle" or "fundamental problem"? Sorry but your language is very condescending.
Sometimes it's easiest to hit Cmd+Z a few times to check out how something looked at an earlier stage of a change. And then it's easy to Redo everything back to the current state. Rewinding changes back can even jog your memory as to why you did something. It's fast, simple, and effective. What's so bad about that?
This is honestly the feature keeping me on PyCharm over VSCode the most. The "local history" feature of JetBrains IDEs is amazing. It saves on every blur, and has a diff viewer when comparing with history.
What’s funny is that Ctrl+S does absolutely nothing in IntelliJ-based IDEs - all changes are saved automatically there, but we all still press it, because old habits die hard - and IntelliJ creators know this and don’t bind Ctrl+S to anything by default.
> Want to know what we saw developers doing instead? They either duplicate code files or took screenshots of relevant code while in the middle of a change. Even I have done something similar before: I'm about to mess this up... I'll Ctrl-A and Ctrl-V this into a new tab before it gets too messy, and then I can put the window beside my editor to use as a reference. I even observed a professional developer with 20 years experience doing this!
I do this kinda stuff all the time in my digital art practice. Duplicate a layer, hide the duplicate, start making changes. Does it work better? Awesome, delete the duplicate. Did my idea not improve it after all? Cool, delete the new version and rename the duplicate back.
It's simple and reliable and builds easily on existing structures.
I made a similar comment and now I recognize that what we’re doing is branching outside version control. And I also recognize it’s still sensible to do, and that to the extent version control feels like friction this is yet another case where the interfaces to it aren’t good enough.
And I don’t just mean “git cli is a bad ux” (though I mean that also), but that the whole world of version control is poorly serving rapid prototyping and other exploratory flows.
It’s pretty likely aliases could serve some of this by wrapping a lot of fast paced idea checkpoints into a set of git actions. But it still would be disruptive for eg any intermediary file system side effects if it’s not faster than whatever watcher you have running.
if git was just ctrl+s or something similar (i.e. no more than a single keyboard shortcut sequence) then this whole problem would disappear, but because even in vscode it's "navigate to source control tab, ctrl+enter, enter enter(if something is not saved) "type some stuff" enter" it's too much to save "tiny" changes. not to mention reverts are a hassle.
because of this comment I just decided to create a keyboard shortcut for commits to be alt+cc so now it's a little easier, I still need to type a commit message and accept save and stage, but at least it's all doable from the keyboard.
> I'll walk through some reasons why version control does not save the day here. While a developer is making changes to code, they may not realize that they want some intermediate version from a few minutes ago until they are well into making the change and become stuck. We saw this repeatedly in our studies. This introduces a problem of premature commitment3, which forces the developer to decide to save an intermediate version (or not to save it) before they have the information needed to make the decision (whether they will need it or not). Unless you commit code to your git repository every few minutes, working or not working, then version control won't help you here.
Presuming DVCS here; if you’re still on a centralized VCS this is surely Not Good:
So, you need an editor utility that automatically commits every save (and possibly autosaves and commiys every completed undo-list item)on a special temporary branch that gets deleted when you do a real commit, and if you go back to prior state on that branch it automatically does the same but starts a new temporary branch with the next item, deleting the whole temp tree on a real commit. Or maybe leave the temp branches around until manually cleaned up, but provide a UI to do that in an all-at-once sweep.
The obvious challenges seem to be managing temp-branch vs. “real” current branch state in a way that doesn't mess up or get messed up by other tools interacting with the repo (including ha sling things like pulls and other inbound changes to your “real” active branch), and generating non-noise automated commit messages. Advantages would be all the tools you have for VCS history can then be deployed to your edit history without any additional friction.
> The obvious challenges seem to be managing temp-branch vs. “real” current branch state in a way that doesn't mess up or get messed up by other tools interacting with the repo (including ha sling things like pulls and other inbound changes to your “real” active branch), and generating non-noise automated commit messages.
Low-level plumbing commands, if the VCS has them, can help with that, e. g. `git commit-tree`.
It’s a convenient shortcut to take an existing commit and append to it a new commit that changes the file tree to any state you want. For example, if you want that new commit to result in the exact same file tree as another existing commit in the same repository, you can tell git commit-tree to make exactly that happen.
It will also just print the new commit hash without moving `HEAD`, which is why it may also be useful in dragonwriter’s use case.
423 comments
[ 2.2 ms ] story [ 316 ms ] threadThat being said I don’t mind committing often. If I have a slew of small commits I squash them.
Since I will need to commit at some point, I rather do it incrementally.
By "diff view" I mean I can see all the changes I've done from the main branch.
I suppose I can stash my current code, reset to where I started somehow, and then unstash.
I've also toyed with the idea of having two repos on my disk.
Having a slider/timeline does sound like an excellent idea. Also just being able to non-destructively go back to a previous state and have a peak without changing the files I'm working on right now would would really well for me I think.
Of course git is an answer to this, but I don't want to think about making a checkpoint.
By default, Emacs has a linear undo structure, while still allowing you to never lose history.
The undo-tree terrifies me.
The source for undo-tree contains documentation which very effectively describes the way the library works with examples and comparisons with how Emacs does things by default: https://gitlab.com/tsc25/undo-tree/-/blob/master/undo-tree.e...
More generally, for anyone new to Emacs and struggling as I once did, I can't recommend strongly enough that you learn how to use the help system (C-h C-h) and the built-in manual (M-x info). Emacs can teach you a great deal about itself, and these are the ways it does so.
By default, you can select any block of text and the undo command will cycle through the changes only from that region of your buffer.
does it understand syntax boundaries like curly braces / functions? ('undo within function' would be key). or just line #?
For eg, see https://www.johndcook.com/blog/2017/08/09/selecting-things-i...
EDIT: Or are you programming in APL?
Another vanilla feature I sometimes use to keep track of old code is the "kill ring". Just cut the old code and it will be available in there later if needed.
[1] https://github.com/jackkamm/undo-propose-el#screenshot
At least 10 years using it. What's interesting though is that such a feature would be off the radar for other editors and even the OP article.
Maybe a little clunky, but works well for me.
Doesn't help with IDE settings and such, but that's such a niche situation that it doesn't matter at all for me.
In vim you can do:
to see the code you had 5 minutes ago.> (1) If you go to a prior state and then make a new change, you can no longer redo and all those changes are lost.
Vim makes a tree, so that doesn't happen with it. When you undo and make a new change, you're just making a new branch. u/Ctrl-r goes from leaf to trunk, but with g-/g+ or :earlier/:later you can walk the whole tree.
Emacs similarly doesn't lose changes in this scenario, but instead of making a tree, it has a ring and undo is an action that can be undone.
> (2) You can not see a side-by-side comparison of the previous version and the latest version.
It's probably not complicated to make a macro or function where you go to a previous version, copy the buffer, paste it in a new one on a new window, return the original buffer to the state you were in, and diff the windows for that side-by-side comparison.
A command like so suffices:
> (3) There is no visual indicator of where you are are in your undo/redo history.Not by default, no. :undolist provides some info, though, and the vimscript undotree() function probably provides all the state of the undotree. There might be plugins that somehow present this info in the interface.
> (5) I have found many actions in code editors that do not get added to the undo stack (e.g., changing a debugger option), which caused me problems in the midst of an annoying bug.
In vim, only stuff that changes the buffer is added to the undo tree. Navigation or changes of the state of the editor (e.g. editor options or vimscript variables) aren't added. Are there really editors where adding actions that aren't changes make sense?
> (6) There is no indication of what steps were "big" or how long ago they happened.
That's also true in vim it seems, at least how "big" they where. Each step does have a timestamp, though.
> (6) It is tedious to backtrack one small step at a time.
You can backtrack by however many steps you want. :earlier also supports specifying by seconds, minutes, hours, days, or file writes.
Most cool of all is that vim can persist the undo history. I don't know how common that is among editors, but in vim you can go to a file you haven't opened in years and undo it all the way to its beginning.
It automatically commits after every change since the IDE was opened, and can easily be diffed and reverted as needed. Reverting saves another entry, so you can revert back to the future as well.
1. Realize I need to check how my new code looked like 5 min back. 2. Copy full file into clipboard, undo by a few steps 3. Do a temp commit on GitHub desktop 4. Paste the new code back and look at the diff on GitHub desktop, and if needed undo the temp commit from above.
Is this more clicks than the vin shortcut? Sure. But I don't have to go learn the internal tree structure representation of my text editor and spend my time in that universe. I have found a way to do what I need to with the tools I have in hand.
This is the same way how the majority of finance runs on excel when they could do better with better programing. In the end whether you get the job done in fields where you're not billed by the second is not dependent on how well you use your tools necessarily.
The other analogy is cars - for most people it's something that gets them places, for others it's a way of life if not at least a more involved proposition.
But if code is your way of life, which it is for many people here, it is beneficial to learn the tools that allow you to do it easier. If someone can see how their code was 5 minutes ago in a few keystrokes, they’re going to take advantage of that significantly more frequently than someone using your cumbersome method.
But for me, every little thing I learn in vim pays off... a thousand times? Macros alone have probably saved me a cumulative few hundred hours. And with every other little thing I learn, compounds with this effect. I write macros much better than I did five years ago, but I still surely have tons of stuff to learn. No IDE will ever compete because vim is just four keystrokes away at any time, no matter where my terminal is.
Personally I have found Vim more approachable than IDEs actually. It doesn't throw all those overloaded toolbars at you according to some one-size-fits-all principle. For me it went so far that last year I actually did a Java project in Vim instead of an IDE for the first time, because the latter had grown to be so frustrating to use.
Also, if you go cold turkey, you'll be productive in less than a month, and arrive at a good workflow that fits you in less than six months, flow that you will improve over the years in amazing ways.
Likewise, if you have any recommendations for your preferred workflow I'm sure some will find it useful. Hopefully those who don't use your preferred $EDITOR don't then rush to complain that not everyone uses $EDITOR. ;)
> There might be plugins that somehow present this info in the interface.
The undotree vim plugin [1] does this, and gives both the file at the time as well as a diff of what changed.
[1]: https://github.com/mbbill/undotree
I was going to mention this plugin. I haven't fully mastered quickly jumping around in it, but it's one of those things that when it's useful, it's VERY useful.
nnoremap u g-
nnoremap <C-r> g+
It takes a little bit of getting use to, but it takes care of most instance where I need a previous edit from a minute ago.
Adobe Photoshop is one. Selection changes don't modify the document at all, but they are part of the undo stack. That's because selecting a part of an image isn't as trivial as selecting text.
Sometimes I wish the current selection could be automatically saved in the file too so you could start where you left off if you were halfway through making a complicated selection (I know you can manually save selections, but that's an added step).
Emacs default undo system is really, really bad. Like worse than the "standard" one. But after installing the emacs undotree it becomes sane.
I wouldn't say that it's worse than the standard, though. Unwieldy as it may be, I like that I can just keep on pressing C-/ and eventually get to the state I want without risk that I may have lost it because I accidentally made a change after a series of undos.
> Emacs documentation recommends using C-f (forward-character) to switch to redo mode, but in my opinion attaching side effect to movement is awful.
I use C-g (keyboard-quit) for that, which is the general let's-not-do-this-anymore keybinding. I think the documentation mentions C-f as an example keybinding that does the needful without much thought on being the "best" keybinding for it.
Are you aware of the `undo-only` (and `undo-redo`) functions? They make it a breeze to navigate even the most complex undo history.
(undo-redo) doesn't seem to be defined in stock Emacs, but undo-tree defines (undo-tree-redo), which does the needful.
My comment was more about lamenting the defaults in comparison with vim's, but it's true that it doesn't take much configuration to make it much better.
I absolutely love this feature. I regularly open a file and undo/redo to figure out where I was editing last.
I can't imagine it'd be that complicated to add it though, if you really want it. I think it'd just be a matter of hooking to the undo function and maintaining a list of timestamps for each item added to the undo list, then adding a command that lets you specify the number of minutes and figures out the correct undo item it should go back to by using that list of timestamps.
There is also a command history, and jumplist (movement cursor position history) for things that don't go in the diff tree.
https://utk-se.github.io/CodeRibbon/
Ah hah. I'd like to use the code history slider plugin too. You've done well to identify a common problem many of us face.
I can't imagine it would be too crazy for IDE's to begin to add the same, per file at first but maybe also per project and then with saving some of those trees.
https://iterm2.com/features.html
Before anyone comments, git doesn’t cut it for this because no one commits after every -+1
EDIT: And of course I read the article after reading the comments.. Haha, VS Code plug-in in 3.. 2..
not super polished nor is it actively maintained but has promise
I use it with every project and it works great. Helped me out a bunch of times. Just need to remember to ignore the .history folder from git and other tools.
https://marketplace.visualstudio.com/items?itemName=Wattenbe...
It is indeed difficult sometimes to understand our own code some time after it is written, but by following good practices (ideally functional, with mostly pure functions), it should be readable.
I will venture a (biased) guess that one of the biggest challenges to understanding the code was the mutation of objects in the process. This is the #1 reason why functional programming is superior. With FP, you can grok what a function does and then comfortably forget about how it does it. It takes input, it produces output. Period. As long as you agree with the transformation algorithm, you can reduce that complexity to a one-line description.
Even in non-FP-first languages, you can usually write pure functions. Some scenarios have enough performance demands that you must mutate things, but probably most situations can be approached with FP styling.
All this rewinding and undoing is a distraction from the real problem. The real problem is that "it smells". It smells like a level of complexity that should not be.
Not to be harsh, but I really think that this indicates some fundamental approach issue rather than a tooling problem.
If it cannot be written on paper (or tablet note with pen) as rough pseudocode... or even drawing boxes and arrows for the visually-inclined, then the problem is not understood. Take what is understood and codify it, pushing the complexity and the unknowns to the edges. At least that reduces the complexity.
One aid I do lean on heavily though is a REPL. Load the code and do some interactive work with it. Inspect the data, test some transformations, and then write code to do what works. That's my current approach to situations that I cannot immediately and accurately write in one pass.
Honestly, this sounds mostly like what you're chastising, except you're doing it outside of the main workflow.
This seems to be a fine example of that.
(Though I must admit that consternation about how often someone presses Ctrl-Z is a bit lower level than I’m used to seeing.)
Have you ever had to build an os kernel, a database, an mmorpg server, a realtime 3d game engine, a production compiler, jit, or garbage collector?
And sure, it may be long ago, but I have some code in a AAA game. Even C++ with hundreds (*edit) of files, we got along just fine without rewind.
When I get thrown into an un-typed python codebase, my productivity grinds to a crawl, and I start having to do things like have a scratch pad with various versions to copy paste.
> I'll Ctrl-A and Ctrl-V this into a new tab before it gets too messy
I did this about 400 times today
That being said, I don't think the reasons viven are honest: > When asked why they did this, they revealed they were trying to view some intermediate state of the code in the middle of a change.
Looking at some GitHub repos, with one single commit, I believe many devs are afraid someone could see all their trial and error attempts and judge them.
I oersonally am too lazy for the undo redo nonsense. Many of my final commits are mostly cleaning up the code. E.g. Refactoring so my code is like reading a story.
I don't have an issue with people thinking I have OCD or something similar. My bigger issue is if I ever have to touch my code again, to waste time on trying to understand it. Or even worse, someone else not understanding it and asking me for help.
This is certainly part of the reason I do it. Sometimes I'll start repos anew, without the "mistake" commits and branches, and add that version to my Github before I send the link to a potential employer.
https://git-scm.com/docs/git-merge
C-f squash
You can compress a series of commits into one with squash in git.
Are most modern developers struggling to write code without this? If so, there is a fundamental problem that has nothing to do with levels of undo.
make change => see result => cmd-z 1 time to fix result OR commit and keep coding
oops, it wasn't really fixed. cmd-z 3 times and see if that works
I know it's wrong, but what's right?
Nah, it's not wrong. You're testing your code changes live.
(I know you're joking)
The closest imaginable thing I might do is wrapping some block of code in an if(false) {..} or #ifdef it out, when I'm refactoring and feeling unsure about my changes. But is that even the same thing that we're talking about here?
Can't imagine how anyone doesn't do this - unless there's just a race of God-programmers I've never encountered that write everything perfect the first go. But I don't think so. :-)
All versions controls have a way to compare, merge and revert historical changes in and out of the current working copy.
I do all of these. I’d use undo/redo when I want to look at something I /just/ did, meaning while in the process of writing something. The git gutter when I stumble upon some change I want to see what was there before. Or Sourcetree for a full diff.
But also, `git diff`.
On one hand it's nice that there are tools to support devs who get lost in their undo-redo history, on the other I feel like it's a matter of good habits to not even have this problem.
of course changing habits is hard, so maybe tooling is justified in this case. I'm just happy I don't have nother "history" type mental model to deal with.
Git stash can be helpful. Stash your "wrong direction" change, then apply just the good chunks from your stash.
Yes, git diffs help, but once I’ve passed a parameterized suite of unit tests, never needed to undo/redo. Maybe not perfect the first go, but only need one/two tries to working code. Then refactor for readability.
Consider, for example, a bug recently introduced during a refactor at my work:
The programmer optimized a conditional based on a regex by transforming it into a simple string compare. All the tests passed, code/branch coverage was good. Except that he missed that the regular expression tested case-insensitively, and our test suite didn't test upper and lower case scenarios.
This simple mistake outlines a few flaws:
- coverage was not robust, because it didn't take into consideration the branches inside the regex (which one could interpret as a form of macro expansion)
- the limited number of inputs used to test failed to capture the broader domain of possible input
- the test did not reflect a successful refactor
Obviously this is a fairly complicated example despite a pretty simple change, and several pieces had to fail in order for this change to fail. But it affirms that even basic changes to code aren't necessarily adequately covered by TDD.
Yes, the developer who forgot to test different letter cases made a mistake. Yes, regular expressions bring their own problems. But fundamentally, the result was that passing the tests did not affirm fitness of the change. Rather, it only proved a limited subset of conditions were error-free.
Effectively, tests are loaded with false negatives, so trusting them to identify problems should be done with a massive grain of salt.
If the developer had simply copy/pasted the code he changed and compared the two, then it's exceedingly more likely that he would have noticed that his code didn't capture the full breadth of conditions in the previous code.
A bug existed in the test suite, to start, and then a bug was introduced into the codebase. A human being looking at the two lines of code as it was rewritten probably would have noticed the regression. But even a code review missed it because it was a fundamentally small change among the other, more "make sure this looks good" code.
Personally, I very much value copy/paste/compare changes and never treat the test suite for anything other than "well we haven't broken anything in any exceedingly obvious ways." Maybe you're a superhuman programmer, but I'd lean more towards "you've probably added more bugs than you realize".
The caps vs. no caps issue would be easily caught using randomString() as the test case.
This is from an example I did just yesterday.
Let’s say you’re moving a piece of code by cut n paste. You cut it but in the middle of changing you copied something else, maybe accidentally. Undo and cut it again. Redo and it’s now back in your clipboard.
You could copy from the diff but you’d get extra tokens to clean up.
If I'm reading this correctly it means applying a large number of undos to get to a previous editor state, followed by an equally large number of redos to then get back to where you started.
I can't recall every doing something like this.
The only time I seem to use undo is when I genuinely make some sort of editing mistake.
One reason I know I don't subconsciously do this is because I never struggle to execute an undo action.
However, on the rare occasions I need to do a redo I tend to struggle with the short cut key and usually go to the menu instead.
That tells me I use undo much more than redo.
Sometimes it's easiest to hit Cmd+Z a few times to check out how something looked at an earlier stage of a change. And then it's easy to Redo everything back to the current state. Rewinding changes back can even jog your memory as to why you did something. It's fast, simple, and effective. What's so bad about that?
I think you simply don't understand the workflow... when and why people do this.
Generally, the idea of referring to an older version of code while working on a later version is good and useful, is it not?
This is just one way of doing this.
NetBeans has the same feature. I cranked it up to keep it for 30 days. Very handy. It's essentially an "automatic git" on every save.
I do this kinda stuff all the time in my digital art practice. Duplicate a layer, hide the duplicate, start making changes. Does it work better? Awesome, delete the duplicate. Did my idea not improve it after all? Cool, delete the new version and rename the duplicate back.
It's simple and reliable and builds easily on existing structures.
And I don’t just mean “git cli is a bad ux” (though I mean that also), but that the whole world of version control is poorly serving rapid prototyping and other exploratory flows.
It’s pretty likely aliases could serve some of this by wrapping a lot of fast paced idea checkpoints into a set of git actions. But it still would be disruptive for eg any intermediary file system side effects if it’s not faster than whatever watcher you have running.
because of this comment I just decided to create a keyboard shortcut for commits to be alt+cc so now it's a little easier, I still need to type a commit message and accept save and stage, but at least it's all doable from the keyboard.
Presuming DVCS here; if you’re still on a centralized VCS this is surely Not Good:
So, you need an editor utility that automatically commits every save (and possibly autosaves and commiys every completed undo-list item)on a special temporary branch that gets deleted when you do a real commit, and if you go back to prior state on that branch it automatically does the same but starts a new temporary branch with the next item, deleting the whole temp tree on a real commit. Or maybe leave the temp branches around until manually cleaned up, but provide a UI to do that in an all-at-once sweep.
The obvious challenges seem to be managing temp-branch vs. “real” current branch state in a way that doesn't mess up or get messed up by other tools interacting with the repo (including ha sling things like pulls and other inbound changes to your “real” active branch), and generating non-noise automated commit messages. Advantages would be all the tools you have for VCS history can then be deployed to your edit history without any additional friction.
Low-level plumbing commands, if the VCS has them, can help with that, e. g. `git commit-tree`.
It will also just print the new commit hash without moving `HEAD`, which is why it may also be useful in dragonwriter’s use case.
Raymond Chen explains it way better than I do: https://devblogs.microsoft.com/oldnewthing/20190506-00/?p=10...
List of related blog entries: https://news.ycombinator.com/item?id=20620441