1. 40
  1.  

  2. 25

    It’s definitely bad when you have to untangle a day of coding (sometimes only an hour) just to find out that too many things changed. But I’m not convinced that planning would help me with anything. The mess that you call thoughts is not something I can plan.

    1. 8

      I tried for a few years to get into the atomic commit thing, but I’ve been in the scenario you describe enough times to have pretty much given up on it for everything but minor patches and configuration tweaks. When I’m working on a new feature, I just leave a trail of git commit -m 'WIP' breadcrumbs for myself in a branch (or more than one branch if I’m trying multiple approaches) and then git reset --soft origin dev && git commit with a detailed commit message when I’m ready to open a PR. In the course of mostly abandoning atomic commits, I’ve inadvertently stumbled into a couple of benefits:

      1. When using git bisect to find a breaking commit in a production branch, intermediate commits that only partially implement a feature often break the build, so you don’t really know whether you’ve found the culprit for a bug or just a WIP commit.
      2. Now that we’ve tried it both ways, my team and I better understand our repos at a high level by looking at their commit histories than when we polluted our logs with every tiny decision we ever made.
      1. 7

        If you have a commit that breaks the build, then it isn’t very atomic ;)

    2. 18

      This is exactly the sort of reason I use magit. The interactive rebase is phenomenal. I regularly reorder, fine-tune and touch up a series of commits before pushing.

      1. 10

        I came to the comments just to say this. Sometimes people look at good GUI git tools (like magic) and scratch their heads and ask ‘why?’. This is the reason why.

        1. 2

          I have recently started using magit, butbI don’t find it significantly different from normal interactive rebase? The keybinds are a bit nicer

          1. 2

            I use magit-commit-instant-fixup a lot. There are equivalents in regular interactive rebase, but in my experience they’re nowhere near as fast (I sometimes do Instant Fixup very frequently while cleaning up a branch with a number of atomic commits, and not wanting to break the order or create a bunch of “fixup” commits to rebase in one go later.)

            I think the main advantage is being able to edit, stage, fixup in the same interface without any significant context switch.

            1. 1

              Completely agree, magit is just amazing because it’s so well thought through, and doesn’t require context switching. For me, the fact I can interact with the version control system using the same verbiage (key strokes) as the editor helps so much.

            2. 1

              I stage chunks and then make a commit, that’s my magit solution to the original post. I also use smerge and ediff3 for merge and rebase conflicts. I had to read the magit manual to learn all the functionality, maybe that’ll help you?

              1. 1

                What’s the advantage over good old git gui, and maybe a good graphical merger like kdiff3?

                1. 2

                  Part of the advantage comes from keeping everything in emacs, and is therefore self-reinforcing (i.e. keybindings baked into muscle memory, etc.) I use Meld a bit sometimes for “overview” diffs, but if you already know emacs then the workflow of resolving conflicts with magit-ediff-resolve is super fast - full keyboard shortcuts, fully integrated in the editor, etc.

          2. 17

            I have often wished for a “multiple index” feature for exactly this reason.

            That said, once you are comfortable with interactive rebasing, amending commits, and so forth, the cleanup process isn’t that bad. It generally takes me 5 minutes or fewer.

            1. 3

              I normally use stashes for that.

            2. 12

              This blames git unfairly. The reason why a developer ends up with such a huge change set is because the fixes were ad hoc and unplanned.

              git at least allows you to pick lines to stage, allowing you to split the huge change set into smaller and more logically cohesive ones. IDEs like VS Code help a lot in this.

              If we were perfect we would do something like make a WIP commit, branch, make and commit the ad hoc changes, return and keep working, amending the main commit as needed, then merge in the ad hoc fixes in the end.

              1. 7

                Howdy, I’m the author. You’re probably right, I should have chosen a less inflammatory title. However, I do think it’s important to think critically about our tools, even the untouchable ones like git, and improve them whenever possible.

                1. 5

                  Why shouldn’t developers expect their VCS to naturally handle the sorts of ad hoc unplanned fixes that naturally happen when working in a codebase on some other change? I wouldn’t necessarily “blame” git for not doing so (insofar as it even makes sense to personify a code tooling program). But this really is a missing feature from git; and it would be nice if git could be modified to handle this common use case better, or if another tool could be used to track changes in this way. There’s no rule that git as it exists right now is the perfect VCS system.

                2. 10

                  My usual git workflow is sort of like what the author describes but I use git add -p and consider the entire branch a working draft until the changes have been made public. Commits can usually be changed quite painlessly with —fixup or —amend so I don’t fret about getting them right the first time

                  This also allows me to easily undo a set of related changes while I’m working by deleting commits, which I find much easier than hunting down the relevant changes manually or attempting to “undo” back to a desired state.

                  I haven’t found that this breaks my flow; I guess I consider staging changes part of the workflow, just like opening a file or editing a buffer. I see how it could be disruptive if your editor of choice doesn’t have git integration so you have to context switch every time you want to stage a change. (I’m using Vim + Tmux)

                  1. 6

                    This is one of the nice things about Perforce. You can do this git plan workflow already by creating a pending change list and giving it a description. Just drag files you’ve marked for edit/add/delete to the appropriate pending CL.

                    The one improvement I’d make to this git plan extension is the ability to add changes to each plan incrementally.

                    1. 1

                      That’s a good suggestion. Good tool over all.

                    2. 5

                      git rebase is a great tool for fixing up whatever mess you did while developing. Now I don’t even consider commit messages before making sure my changes work, and then do a git rebase session to make the commit history sensible to others.

                      1. 1

                        I long ago thought about a GUI tool visualizing the git history as a tree, where you’d be able to drag & drop commits around, up & down a branch and between branches, and also easily split commits. (I.e. git rebase -i but more streamlined/actually interactive.) Didn’t try working on it, unfortunately, too many other unfinished projects, and I’m in a local maximum of being relatively fluent enough doing this in CLI git.

                        1. 2

                          Ungit might be a tool you want. Not quite that strong, but very nice to work with history in a visual way.

                      2. 5

                        This is fairly similar to the workflow that Stacked Git encourages, which I’ve used every day for the last several years. It’s really helped me in having focused commits, because I’ve already stated up front what will be in each individual patch.

                        You can create a new commit (at “patch” in Stacked Git terminology) by providing a name, and then filling out the commit message all the way here at the beginning.

                        $ stg new -- patch-name
                        

                        Then you can work on your changes like normal, and when you’ve got them, you can add to the staging area and then add the staging area to the patch at the top of the stack.

                        $ git add -p
                        $ stg refresh -i
                        

                        If you want to make a change not describe by your current patch, you can make a new one, and then use command to manipulate the stack.

                        $ stg pop
                        $ stg push
                        $ stg goto patch-name
                        

                        With a few project-specific exceptions, I only work off of the main branch, using the stack as the exclusive means of working on changes. To interact with systems expecting branches (like GitHub and Bitbucket) I manipulate the stack to reflect the desired branch (often, just a single patch) and use an alias that pushes the top patch by ID to a branch named after the top patch.

                        $ git push -f origin $(stg id):refs/heads/patches/$(stg top)
                        

                        Once changes are merged upstream, I can rebase patches on top and remove ones already merged.

                        $ stg pull -m
                        $ stg clean
                        

                        I haven’t done any scientific testing, but I feel I’m more productive with this system than I ever was trying tease commits apart and squashed together with git commands manually.

                        1. 1

                          I wanted to try Stacked Git for a while now, and installed it yesterday. I think I will give it a real try, as your comment really resonates with the workflow I have in mind. Thanks!

                          Is there a way to easily reorder patches in a stack? I see stg float and stg sink exist.

                          1. 1

                            AFAIK, there’s no interface for reorganizing the patches in a series like you might do in your editor with an interactive rebase. I’m very rarely working with a series large enough that working with stg push, stg pop and stg sink feels too limiting. If you are, I’m curious to learn more, and what solutions (if any) you come up with.

                        2. 4

                          In many GUIs you can write the message before you begin, and stage while you work. I suppose I sometimes “plan” like this in Sublime Merge.

                          1. 3

                            The “right” thing to do is to spend time un-tangling your changes, staging them in groups and committing with well thought-out commit messages

                            Why?

                            git commit -a -m "<describe your changes>"

                            There is this assumed notion, never much justified, that commit histories must be a pristine sequence of logically consistent additions.

                            And yes, it would be slightly nicer if they looked like that. But the actual benefits of such a history are at best marginal, because commit histories just aren’t looked at that much. Or at least shouldn’t: if you have an intense relationship with your or your colleague’s commit histories, you need to look at your development practices. IMHO, they’re actually harmful.

                            Because development doesn’t actually work like that, and logs should track what actually happened, with as little filtering as possible, rather than what you (retroactively) wanted to happen. Just imagine tracking a bug in a program using logs and the program edited those logs retroactively to reflect what it thought should have happened. It would be impossible.

                            And in addition to being directly harmful, you also need to expend effort creating them, there is a significant chance of inconsistent commits entering the repository, and you are incentivised to adjust your actual development to reflect this unrealistic model (in order to avoid the extra work) rather then letting it flow.

                            Just say no.

                            1. 3

                              To completely eliminate the context switch, I suppose you could write commit messages in comments in the source code. Choose a special syntax that denotes a commit message, like ///, and then you could create a tool that goes through the source code, finds all the commit comments and creates commits containing the code following each comment. Or something like that.

                              1. 5

                                Hey, I’m the author and I actually built that too: https://github.com/synek/codeline

                                It’s earlier in development though, so it’s technically not “released” yet.

                                1. 2

                                  Neat! Looks interesting.

                              2. 2

                                I’ll often just commit unfinished work if I realise I’m going to context switch, work on the second thing, then just amend the first commit, and possibly reorder.

                                1. 2

                                  I’m a big fan of git-revise, especially for the purpose of splitting commits:

                                  https://github.com/mystor/git-revise

                                  It is a special-purpose rebase command for rewriting local history.

                                  You use it like rebase -i, but it has an additional cut command for splitting a commit. It goes through hunks interactively (like git add --patch), and you can split hunks to some extent, but it also has an edit mode, where everything is possible, so you can split out changes to the same line.

                                  The impressive thing is that it works without touching your checked out files (so you can compile at the same time), and it can’t change the final state, which is often good to know.

                                  1. 2

                                    I can relate to this problem and I am glad there’s some creative work in this area.

                                    The result of this workflow is still a chain of commits in a single branch. That might be ok for a solo project, but in a team environment, the workflow usually calls for separating topics by branch, so they can each be reviewed as a separate pull request (or similar review tool). I guess one could cherry pick changes into different branches after the fact, but could that have a role in this tool?

                                    My usual workflow, when making multiple unrelated changes to a repo, is to use git worktree to get isolated directories in which to make each change. Each worktree is a separate branch, which also makes it easy to push each set of commits separately, to be reviewed as PRs.

                                    1. 1

                                      Hey I’m the author. I think it could be extended to support a more branch-based workflow.

                                      When you plan a commit, the working branch is stored with it. In theory, the git plan commit command could switch you onto the plan’s branch first, before creating the commit.

                                      Perhaps when creating a planned commit, you could set a commit_in_new_branch flag to control this kind of behaviour.

                                      I’m cautious about adding too much functionality to the tool, but if you have any other ideas for branch-based workflows I’d love to hear them.

                                    2. 1

                                      I personally use git add --patch rigorously. vim-fugitive and staging hunks whenever there are more complicated changes. This is followed up with git diff --cached (alias git dc) and git commit -v. I also have git commit --amend aliased to do fixups. It’s not perfect and there is a lot of small stuff to learn and keep in mind. But all in all I’m fairly satisfied with my workflow currently.

                                      1. 1

                                        I simply try not to work on multiple features if there are uncommitted changes. When it does happen, I untangle it with Sublime Merge by selectively staging certains parts and not others.

                                        1. 1

                                          When this happens, I turn to Sublime Merge. It makes sorting out messes like that almost fun.

                                          1. 1

                                            While it’s not the default option in git, I think a simpler solution can be used. You can create a commit with no changes with the --allow-empty. With that in mind, you can easily start working with an empty commit and a commit message detailing what you intend to work on; after you stage your changes with git add -p, then you can amend your commit to add the newly staged changes to the previous commit. You even get to rewrite the commit message if you want!

                                            # before working on x, y and z
                                            $ git commit --allow-empty -m “I’m going to work on x, y, and z in this commit”
                                            
                                            # worked on x and y, didn’t get to z, but also worked on w 
                                            $ git add -p
                                            $ git commit --amend -m “Complete x, y, and w”