1. 64
  1.  

  2. 7

    How does git9 support staging only part of the changes to a file? From what I can tell it does not.

    I would describe any version control system which doesn’t allow me to commit only some of the hunks in a file or edit the patch myself as “boneheaded.”

    1. 9

      Can I quote you on the boneheaded bit? It seems like a great endorsement.

      Anyways – this doesn’t fit my workflow. I build and test my code before I commit, and incrementally building commits from hunks that have never been compiled in that configuration is error prone. It’s bad enough committing whole files separately – I’m constantly forgetting files and making broken commits as a result. I’ve been using git since 2006, and every time I’ve tried doing partial stages, I’ve found it more pain than it was worth.

      So, for me (and many others using this tool) this simply isn’t a feature that’s been missed.

      That said, it’s possible to build up a patch set incrementally, and commit it: there are tools like divergefs that provide a copy on write view of the files, so you can start from the last commit in .git/fs/HEAD/tree, and pull in the hunks from your working tree that you want to commit using idiff. That shadowed view will even give you something that you can test before committing.

      If someone wanted to provide a patch for automating this, I’d consider it.

      1. 6

        Thanks for this response - its a very clear argument for a kind of workflow where staging partial changes to a file doesn’t make sense.

        I work primarily as a data scientist using languages like R and Python which don’t have a compilation step and in which it is often the case that many features are developed concurrently and more or less independently (consider that my projects usually have a “utils” file which accumulates mostly independent trivia). In this workflow, I like to make git commits which touch on a single feature at a time and its relatively easy in most cases to select out hunks from individual files which tell that story.

      2. 5

        As somebody who near-exclusively uses hggit, and hence no index, I can answer this from experience. If you want to commit only some of your changes, that’s what you do. No need to go through an index.

        Commit only some of your changes?
        hg commit --interactive
        git commit --patch

        Add more changes to the commit you’re preparing?
        hg amend --interactive
        git commit --amend --patch

        Remove changes from the commit?
        hg uncommit --interactive
        git something-complicated --hopefully-this-flag-is-still-called-patch

        The main advantage this brings: because the commit-you’re-working-on is a normal commit, all the normal verbs apply. No need for special index-flavoured verbs/flags like reset or diff --staged. One less concept.

        If you want to be sure you won’t push it before you’re done, use hg commit --secret on that / those commits; then hg phase --draft when you’re ready.

        1. 2

          Actually sounds pretty good! Anyone know if such a thing is possible with fossil?

        2. 4

          You can do it like hg does with shelve - always commit what is on disk, but allow the user to shelve hunks. These can be restored after the commit is done. Sort of a reverse staging area.

          1. 3

            I haven’t tried git9, but it should still be possible to support committing parts of files in a world without a staging area. As I imagine it, the --patch option would just be on the commit command (instead of the add command).

            Same with all other functionality of git add/rm/mv – these commands wouldn’t exist. Just make them options of git commit. It doesn’t matter if the user makes a commit for each invocation (or uses --amend to avoid that): If you can squash, you don’t need a staging area for the purpose of accumulating changes.

            Proof of concept: You can already commit parts of files without using the index, and without touching the workspace: Just commit everything first, then split it interactively using git-revise (yes, you can edit the inbetween patch too). I even do that quite often. Splitting a commit is something you have to do sometimes anyway, so you might as well learn that instead. When you can do this – edit the commit boundaries after the fact, you no longer need to get it perfect on the first try, which is all that the staging area can help you with.

            Rather than a staging area, I wish I could mark commits as “unfinished” (meaning that I don’t want to push them anywhere), and that I could refer to these unfinished commits by a never-changing id that didn’t change while working on them.

            1. 3

              This fits my mental model much better too. Any time I have files staged and am not in the “the process of committing” I probably messed someting up. The next step is always clear the index or add everything to the index and commit.

              1. 3

                -p is indeed available on commit. And also on stash.

              2. 2

                I feel the Plan 9 way would be to use a dedicated tool to help stash away parts of the working directory instead.

                1. 2

                  I would describe any version control system which doesn’t allow me to commit only some of the hunks in a file or edit the patch myself as “boneheaded.”

                  I would describe people wedded to the index in softer but similar terms.

                  Here’s the thing: if you’re committing only part of your working tree, then you are, by definition, committing code that you have never run or even attempted to compile. You cannot have tested it, you cannot have even built it, because you can’t do any of those things against the staged hunks. You’re making a bet that any errors you make are going to be caught either by CI or by a reviewer. If you’ve got a good process, you’ve got good odds, but only that: good odds. Many situations where things can build and a reviewer might approve won’t work (e.g., missing something that’s invoked via reflection, missing a tweak to a data file, etc.).

                  These aren’t hypotheticals; I’ve seen them. Many times. Even in shops with absolute top-tier best-practices.

                  Remove-to-commit models (e.g. hg shelve, fossil stash, etc.) at least permit you not to go there. I can use pre-commit or pre-push hooks to ensure that the code at the very least builds and passes tests. I’ve even used pre-push hooks in this context to verify your build was up-to-date (by checking whether a make-like run would be a no-op or not), and rejected the push if not, telling the submitter they need to at least do a sanity check. And I have, again, seen this prevent actual issues in real-world usage.

                  Neither of these models is perfect, both have mitigations and workarounds, and I will absolutely agree that git add -p is an incredibly seductive tool. But it’s an error-prone tool that by definition must lead to you submitting things you’ve never tested.

                  I don’t think my rejection of that model is boneheaded.

                  1. 6

                    You cannot have tested it, you cannot have even built it, because you can’t do any of those things against the staged hunks.

                    Sure you can, I do this all the time.

                    When developing a feature, I’ll often implement the whole thing (or a big chunk of it) in one go, without really thinking about how to break that up into commits. Then when I have it implemented and working, I’ll go back and stage / commit individual bits of it.

                    You can stage some hunks, stash the unstaged changes, and then run your tests.

                    1. 5

                      Here’s the thing: if you’re committing only part of your working tree, then you are, by definition, committing code that you have never run or even attempted to compile. You cannot have tested it, you cannot have even built it, because you can’t do any of those things against the staged hunks

                      While this is true, it isn’t quite as clear-cut as you make it seem. The most common case I have for this is fixing typos or other errors in comments or documentation that I fixed while adding comments / docs for the new feature. I don’t want to include those changes in an unrelated PR, so I pull them out into a separate commit and raise that as a separate (and trivial to review) PR. It doesn’t matter that I’ve never tried to build them because there are no changes in the code, so they won’t change the functionality at all.

                      Second, just because I haven’t compiled them when I commit doesn’t mean that I haven’t compiled them when I push. Again, my typical workflow here is to notice that there are some self-contained bits, commit them, stash everything else, test them, and then push them and raise a PR, before popping the stash and working on the next chunk. The thing that I push is tested locally, then tested by CI, and is then a small self-contained thing that is easy to review before merging.

                      But it’s an error-prone tool that by definition must lead to you submitting things you’ve never tested.

                      And yet, in my workflow, it doesn’t. It allows you to submit things that you’ve never tested, but so does any revision-control system that isn’t set up with pre-push hooks that check for tests (and if you’re relying on that, rather than pre-merge CI with a reasonable matrix of targets, as any kind of useful quality bar then you’re likely to end up with a load of code that ‘works on my machine’).

                      1. 3

                        I mentioned there are “mitigations and workarounds,” some of which you’re highlighting, but you’re not actually disagreeing with my points. Git is the only SCM I’ve ever encountered where make can work, git diff can show nothing, git commit won’t be a no-op, and the resulting commit can’t compile.

                        And the initial comment I’m responding to is that a position like mine is “boneheaded”. I’m just arguing it isn’t.

                      2. 5

                        Here’s the thing: if you’re committing only part of your working tree, then you are, by definition, committing code that you have never run or even attempted to compile.

                        I mean, sure. And there are many places where this matters.

                        Things like cleanly separating a bunch of changes to my .vimrc into logical commits, and similar sorts of activity, are… Not really among them.

                        1. 1

                          Gotta admit, this is a very solid argument.

                        2. 1

                          That’s a good question. I imagine something like

                          @{cp file /tmp && bind /tmp/file file && ed file && git/commit file}
                          

                          should work.