My new Git utility `what-changed-twice` needs a new name

(blog.plover.com)

88 points | by jamesbowman 4 days ago ago

63 comments

  • GuB-42 3 days ago ago

    Why does it needs a new name?

    I had a good idea of what it did before reading the article, it is a long name but not Java-long, and none of the suggestions so far are clear to me, even after reading the article.

    The only somewhat confusing part is the "twice", because it can be more than twice. But if you think about it, if it has been changed more than twice, it had to be changed twice at some point, so it is not totally wrong.

    • mjd 3 days ago ago

      At the time I started writing the article, the utility was called `analyze-commits`. Hard to think of a worse name than that!

      By the time I finished writing it I had come up with a less crappy name, but I thought I'd leave the question in the post anyway.

      • antonvs 3 days ago ago

        If you’re looking for something descriptive and not clever/catchy, I propose ‘find-repeat-changes’.

        • 0manrho 3 days ago ago

          Just gonna +1 this. It's still fairly short, descriptive and to the point, which I generally prefer to something more "trendy" or "clever". I like it.

        • 1718627440 2 days ago ago

          What about git n-changed or even git nchanged. I feel like these commands need to be short and not consist of >3 words.

        • alex-moon 3 days ago ago

          Or indeed "Find Repeat EDits" or fred for short.

        • 3 days ago ago
          [deleted]
  • codewritero 4 days ago ago

    Jujutsu has a command which is helpful for this sort of workflow called absorb which pushes all changes from the current commit into the most recent commit which modified that file. (Each file may be merged into a different commit).

    • schrodinger 3 days ago ago

      This seems very similar to how I work by default. I sort of think in terms of "keyframes" and "frames", or "commits" and "fixes to commits."

      Whenever I sit down to code with a purpose, I'll make a branch for that purpose: git checkout -b wip/[desc]

      When I make changes that I think will be a "keyframe" commit, I use: git add . git commit -m "wip: desc of chunk" (like maybe "wip: readme")

      if I make refinements, I'll do: git add . git commit --amend

      and when I make a nee "keyframe commit": git commit -m "wip: [desc 2]"

      and still amend fixes.

      Occasionally I'll make a change that I know fixes something earlier (i.e. an earlier "keyframe" commit) but I won't remember it. I'll commit and then do: git add . git commit -m "fixup: wip desc, enough to describe which keyframe commit should be amended"

      at the end I'll do a git rebase -i main and see something like:

      123 wip: add readme (it's already had a number of amends made to it) 456 wip: add Makefile (also has had amendments) 789 wip: add server (ditto) 876 fixup: readme stuff 098 fixup: more readme 543 fixup: makefile

      and I'll use git rebase -i to change it to reword for the good commits, and put the fixups right under the ones they edit. then i'll have a nice history to fast forward into main.

      • ZeroGravitas 3 days ago ago

        I think you might be aware given the specific words you use but for the benefit of others:

        Git commit --fixup lets you attach new commits to previous hashes you specify and then can automatically (or semi-manually depending on settings) squash them in rebases.

        • pimlottc 2 days ago ago

          You can combine this with the `:/<text>` syntax [0] for matching the most recent commit with a given text in the commit message, e.g.

              $ commit frobinator/ -m "refactor the frobnicator"
          
              [ more work ]
          
              $ commit echaton/ -m "immanentize the eschaton"
          
              [ oops, missed a typo ]
          
              $ commit frobinator/ --fixup :/frobic
          
          0: https://stackoverflow.com/a/52039150
        • schrodinger 2 days ago ago

          Thanks, I am —- but I always found it easier to just give the new commit a name I know how to squash rather than type in a SHA.

          The other post about being able to do it on a substring match sounds way more ergonomic though, I’ll have to try that!

    • koterpillar 3 days ago ago

      git-absorb (https://github.com/tummychow/git-absorb) does a bit more, figuring out the exact changes that should be fixed up.

      • globular-toast 3 days ago ago

        git-autofixup is better and easier to install: https://github.com/torbiak/git-autofixup

        • operator-name 3 days ago ago

          Could you elaborate how it is better?

          • globular-toast 3 days ago ago

            They are quite different methods, explained by the respective implementations. IME autofixup finds the relevant commit successfully more often. There's no reason you can't use both, of course. I would always check the results of either before actually doing the rebase.

      • CorrectHorseBat 3 days ago ago

        JJ absorb does the same as far as I understand

    • 3 days ago ago
      [deleted]
    • metadat 4 days ago ago

      Yes, totally useful compared to default git base commands.

      And also - melding the "changed twice" (or thrice...) mutations into a single commit is a brilliant isolation of a subtle common pattern.

  • squeaky-clean 3 days ago ago

    "what-changed-twice" tells me exactly what the command does. "squash-what" tells me nothing, why is the program name asking me what to squash, and then why does it not squash? The only inaccuracy I can think of in the name is that it's technically "what-changed-more-than-once." But if something has changed thrice, by definition it's also been changed twice.

    • antonvs 3 days ago ago

      > “squash-what" tells me nothing, why is the program name asking me what to squash, and then why does it not squash?

      ‘squash-candidates’ would address all of that.

    • basemi 3 days ago ago

         what-changed-once-more
  • eru 4 days ago ago

    > There's bonus information too. If a commit is not mentioned in the report, then it only changed files that didn't change in any other commit. That means that in a rebase, I can move that commit literally anywhere else in the sequence without creating a conflict. Only the commits in the report can cause conflicts if they are reordered.

    This is only true in the textual level.

    Semantically, re-shuffling commits like this can still cause conflicts. Ie it can break your tests. Not at the end, but for the intermediate commits.

    • _dark_matter_ 4 days ago ago

      This is why I no longer do atomic commits. I've just never had it be a benefit to walk through and guarantee that each commits tests and builds successfully. I so rarely back out changes that when I do, I test then that everything is working (and let's be honest, I back out usually at the PR level, not the commit).

      • mcintyre1994 3 days ago ago

        The other benefit of this is the git bisect workflow. If you can’t build your intermediate commits then you likely can’t easily identify whether a bug was present on that commit (for many types of bug), and you therefore can’t identify the commit that introduced the bug.

        • eru 3 days ago ago

          Yes, but at least git bisect lets you mark a commit as 'skip' in these cases.

      • globular-toast 3 days ago ago

        I often wonder what the point of using git at all is at this point. I suppose it's just your interface to the source repo, but a massively overly capable one. If you don't care about atomic commits then you might as well just do `git commit -a --amend --no-edit` periodically (you could even do it on every save). Then the reflog is your "undo" but you don't pollute the shared repo with shit commits.

      • eru 3 days ago ago

        If you want atomic commits, you need to set up your CI/CD to ensure that each intermediate commit builds and passes tests.

        Most pull requests should probably be squashed to appear as a single commit in the final history. But you should have the option of leaving history intact, when you want that, and then your CI/CD should run the checks as above.

        • WorldMaker 3 days ago ago

          You don't need squash here, though. If your CI/CD ensures that merge commits (PRs) are atomic/build and pass tests, you can `git bisect --first-parent` to just bisect your merge commits/integration points/pull requests, without tossing the other history from the git DAG.

      • eru 2 days ago ago

        > I've just never had it be a benefit to walk through and guarantee that each commits tests and builds successfully.

        If you never look at individual commits in your history, you might as well squash them.

      • mjd 3 days ago ago

        I agree. I decided years ago that that was a lot of work for little or no benefit.

        It's enough for the tests to pass at each merge point.

        • Supermancho 3 days ago ago

          I would say most workplaces have settled similarly.

          Sit in draft until you're ready to use the CI - which you verified locally or run manually in draft, before convert to reviewable - then review, maybe tweak, merge.

          Atomic commits would endanger me losing unfinished work or eventual dead-ends with no record. This seems inefficient.

        • baq 3 days ago ago

          …and that’s why squash merge should be the default setting in PRs.

          • eru 3 days ago ago

            Yes, it should be the default, but ideally you have the option of preserving history (for PRs where that makes sense) and then your CI/CD should also check that the individual commits build and pass tests.

            In general, your CI/CD should make sure that each commit that appears in the 'public' history of main builds and passes tests.

          • WorldMaker 3 days ago ago

            You can `git bisect --first-parent` just fine without needing to squash.

  • gorgoiler 3 days ago ago

    Tools like this are also useful if you need to cherry pick a patch onto a release branch and want to know potential dependencies:

      ↑ newer
      D* fixes bug in crypto.py
      C
      B* rewrites crypto.sh in Python
      A
      0  last month’s release
      ↓ older
    
    In this example, if the release needs the fix in D you’ll also need to cherry pick the rewrite in B.

    You get false positives and false negatives: if B fixed a comment typo for example it’s not really a dependency, and if C updated a module imported in the new code in D you’d miss it. (For the latter, in Python at least, you can build an import DAG with ast. It’s a really useful module and is incredibly fast!)

    So I would say the author’s tool is really multiple tools:

    1/ build a dependency graph between commits based on file changes in a range of commits;

    2/ automate the reordering and squashing of dependent commits on a private dev branch;

    3/ automate cherry-picking commits onto a proposed release branch (which is basically the same as git-rebase -i); and

    4/ build a dependency graph based on external analysis (in my example, Python module imports) rather than / as well as file changes.

    Their use case is (1) and (2), (3) is a similar but slightly different tool to (2), and (4) is a language specific nicety that goes beyond the scope of simple git changes for, arguably, diminished returns.

  • MontagFTB 4 days ago ago

    I am familiar with an algorithm that stably brings a disjoint selection of items together around a specified point. Sounds similar to this case, where the disjoint selection are changes that happened to a given file.

    The name of the algorithm is “gather”, by Sean Parent and Marshall Clow.

  • zahlman 3 days ago ago

    When I make Bash aliases or functions for Git functionality, I always name them as `git-something-or-other`. That way they're namespaced in a way that I find pleasant both for tab completion and for easy of memory. I think that should apply to more complex utilities, too.

    By my usual naming conventions, this one would be `git-repeatedly-changed`.

    • nothrabannosir 3 days ago ago

      Last but decidedly not least: if you have `git-foo` on the PATH, you can do `git foo` and it will automatically pick up your program.

      If I remember early git days correctly, that's how git was implemented: a bunch of separate utilities working together on the database which is the .git folder.

    • mjd 2 days ago ago

      I usually do that too, but this seemed to me like it's not really a git utility. It's just a filter.

      I can see the argument in favor of `git-` also.

      But I think I'd prefer `git-changed-twice` to be a wrapper that takes a reflist argument, and runs `git-log --stat reflist | what-changed-twice`.

  • handsclean 3 days ago ago

    I suggest group-commits-by-file , group-commits , or group-by-file, depending on whether you want it to make sense out of context and whether you ever group commits differently. You might then feel compelled to add a final line like “… and 12 files with 1 commit each”, or even to enumerate them, which sounds like it’d be useful anyway. “what” isn’t doing any work, there’s already an implicit “what” in the call-response paradigm. “Changed” implies you’re detecting changes, but you’re not, you’re operating on a data structure that happens to represent changes.

  • quuxplusone 3 days ago ago

    Suggestion: `git squash-report`. (Or `git rebase-report`, except I wouldn't call it that because it would interfere with my tab-completion of, and/or muscle memory of, `git rebase -i`.)

  • chris_wot 4 days ago ago

    You know, I find myself partially agreeing that a number of utilities for git could be done quite nicely in perl.

    • eru 4 days ago ago

      Git's repository includes quite a bit of Perl, but they want to get rid of it.

      • chris_wot 3 days ago ago

        Is there any reason for doing so?

        • kruador 2 days ago ago

          It's a pain in the backside to run on Windows, for two reasons. Firstly, Windows doesn't have (by default) a lot of the tools that are preinstalled in most nix environments. Git for Windows ships half a Cygwin distribution (MSYS2) including Bash, Perl, and Tcl.

          Second, Windows doesn't really have a 'fork' API. Creating a new process on Windows is a heavyweight operation compared to nix. As such, scripts that repeatedly invoke other commands are sluggish. Converting them to C and calling plumbing commands in-process has a radical effect on performance.

          Git for Windows is more of a maintained fork than a real first-class platform.

          Also, I believe it's a goal to make it possible to use Git as a library rather than as an executable. That's hard to do if half the logic is in a random scripting language. Library implementations exist - notably libgit2 - but it can never be fully up to date with the original. Search for 'git libification'.

          Many IDEs started their Git integration with libgit2, but subsequently fell foul of things that libgit2 can't do or does inconsistently. Therefore they fall back on executing `git` with some fixed-format output.

          • 1718627440 a day ago ago

            I don't get why everything needs to be a library? Using the OS to invoke things gets you parallelism and isolation for free. When you need to deal with complicated combination of parameters to an API, it doesn't become too different from argument parsing, so you might as well do that instead.

            You can still wrap the interface to the executable in a library.

  • nferraz 3 days ago ago

    Why did you opt for "highly-abbreviated commit IDs"?

    Instead of:

    ``` calendar/seasons.blog 196 40 d1

        196  196e749
         40  40c52f4
         d1  d142598
    ```

    The tool should simply display:

    ``` calendar/seasons.blog 196e749 40c52f4 d142598 ```

    That's it!

    The second table only complicates the output.

    PS:

    `what-changed-twice` is a good name.

  • perfmode 3 days ago ago

    You could shrink the prefixes in your report. 40 and 33 could become 4 and 3 without losing correctness.

    • mjd 2 days ago ago

      There were commits in the original log input for which 4 and 3 would have been ambiguous, and the abbreviations are already short enough.

  • paulddraper 3 days ago ago

    Website is down.

    https://archive.ph/52C1y

  • allseeingimei 4 days ago ago

    git-delta -n <times>

    i.e. git-delta -n 2 = 'what changed twice'

    or if its just what changed twice in every case then just 'git-delta-delta'

  • protocolture 3 days ago ago

    Double Jeopardy?

  • st3fan 3 days ago ago

    Oidia- Oops I did it again

  • atoav 3 days ago ago

    No it does not.

  • nicr_22 4 days ago ago

    FlipFlopStop?

    FFS for short, which has suitably disgruntled other exclamatory meanings.

  • cozzyd 3 days ago ago

    oops-i-did-it-again

  • pfannkuchen 3 days ago ago

    change-cluster?