1. 7
  1.  

  2. 4

    For everyone who needs to work on more than one patch against the same repo in parallel: Instead of cloning the repo twice, you can use git-worktree to create separate directories for those branches. Git then uses file-system links to the original checkout, which helps you save disk space

    1. 1

      Mercurial’s user interface for this:

      hg share [--noupdate] [--bookmarks] [--relative] SOURCE [DEST]
      hg unshare
      

      Git’s user interface for this:

      git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]] [-b <new-branch>] <path> [<commit-ish>]
      git worktree list [--porcelain]
      git worktree lock [--reason <string>] <worktree>
      git worktree move <worktree> <new-path>
      git worktree prune [-n] [-v] [--expire <expire>]
      git worktree remove [-f] <worktree>
      git worktree repair [<path>…​]
      git worktree unlock <worktree>
      

      Haaaaving pointed that out, this is absolutely a ‘bad old days’-style Git UI – recent Git UI work is massively better, and a lot of it is focused on undoing the bad decisions of the past, or at least making better alternatives for them in the present.

      1. 5

        I mean, that seems a little uncharitable, the comparison is more like:

        git worktree add PATH <commit-ish> [-b <new branch>]
        git worktree remove PATH
        

        The other commands and flags are for edge cases or less common scenarios, such as creating a worktree on a different disk that may not always be mounted at the same time as the default worktree; or accidentally moving or deleting the worktree. I’d guess the vast majority of users of the command never use the other subcommands at all.

        I’m not sure they are really 1:1 though, hg share seems to be similar, but more like having multiple clones that happen to share data, but any of the clones can checkout any branch/changeset and modify it, affecting the others; whereas git worktree is more of a parent/child relationship, and the children are isolated to a dedicated branch. The way hg share does it might actually be more useful in some ways, but does have some potential footguns it seems (as called out in the docs).

        All that to say, while I think we can all agree that Git has some usability issues, I’m not sure it’s fair to say this command is one of them. Who knows though, maybe I’m too close to it to be objective.

        1. 5

          That’s a fair comment, and my first reaction was that you were right, that I should have limited my comparison to hg share vs git worktree add.

          So I tried to redo the comparison, this time doing my best to compare ‘like for like’ – but my learning brought me full circle, and comparing ‘like for like’ landed me back at comparing ‘hg share/unshare’ and ‘git worktree + subcommands’. That’s where they are alike: they serve the same purpose, and solve the same problem. The implementations are nothing alike; and it turned out that Git’s differences in UI arise from its awkward design, not from extra capabilities.

          The design flaw in question: git worktree stores administrative files of some kind inside the parent repo when you create a chlld worktree, instead of making the parent unaware of the child worktree/clone. As a result, the user has to keep the parent repo in sync with the child worktree.

          Mercurial, where the child repo shares its repo storage with / points to the parent repo, needs none of those commands. Move the child with mv, remove it with rm, the parent repo doesn’t need to know. But Git, to ensure the parent repo is infomed when you move or delete the child repo, now needs these extra commands:

          • git worktree lock, where ‘lock’ means ‘don’t garbage-collect administrative files’. This is the ‘disk may be unmounted’ thing you mentioned: both a Git and a Mercurial child will be fine, but whereas the Mercurial parent repo will also be fine, the Git parent repo will perform self-breakage unless you use lock to tell it not to.
          • git worktree list lists child worktrees the parent knows about. Made possible by this design, shoing that every cloud has a silver lining.
          • git worktree move, because to move the child worktree you need to tell the parent it moved
          • git worktree prune, because you may have removed child worktrees using rm instead of git worktree remove.
          • git worktree remove, because else the parent may wonder where its child went.
          • git worktree repair, to auto-fix screwups caused by not using the commands above. Does make use of the two-way linkage: if you move the parent repo and then run repair, it will tell the child worktrees where their parent went. But honestly, a repair command is not a great sign, is it?
          • git worktree unlock, where ‘unlock’ means… ’re-enable prune, move, and remove. Does this mean lock also disables those commands?

          Second design flaw: tying a worktree to a particular branch. Now instead of one hg share REPO DEST command, you need seperate commands for

          • create a new branch with the same name as the new dir (git worktree add DEST)
          • create a new branch with a different name than the new dir (git worktree add DEST -b NEW_BRANCH)
          • give the new dir a detached-head checkout (git worktree add --detach DEST)
          • checkout an existing branch in the repo (git worktree add DEST plus some combination of -B and --force, I couldn’t quite figure this out from the docs)
          • all of the above take an optional COMMIT-ISH for if you’d like the new checkout to be of, and the new branch to point to, a commit other than HEAD.

          Bonus: because git worktree add does not have a SOURCE-REPO argument, it can only be run from inside the parent repo. Which explains why the help’s example of creating a sibling worktree runs git worktree add ../myfeature instead of git worktree add myrepo myfeature.

          So, yeah. I swear I’m not some deranged Git-hater. I use it a lot, I’m skilled at using it, I’ve been conditioned to avoid its pain points. It’s just that I know Mercurial as well as Git, and they are both about the exact same thing: managing a DAG of commits, and syncing between repos. So everything Git does, Mercurial does too, and I get to compare. And consistently, and I mean really consistently, Git’s UI is not just worse, but atrocious. And so, every time a piece of Git’s UI is brought up, I feel compelled to point out that It Didn’t Have To Be This Way.

          1. 2

            I absolutely respect your opinion on the design, and appreciate that you’ve spent the time to scour the docs! That said, I do disagree that git worktree being linked to a specific branch/ref is some kind of design flaw. For me, this matches the way I use it, i.e. as a temporary, throwaway working tree in which I can put together a hotfix, push it, and then blow it away. Not only do I not use git worktree for long lived working trees, I don’t even understand why anyone would want to in the first place. After all, that is what branches are for; the only reason to use git worktree at all is if you are in the middle of something that you don’t want to disturb in order to deal with something that has come up suddenly, like a production issue. Perhaps things are different with Mecurial, I never spent enough time with it to find myself using share, so I can’t speak to how its commonly used.

            I would probably consider myself a “Git surgeon” level of Git user, but I have never used any of those subcommands other than worktree add and worktree remove. I’ve directly rm -rf‘d worktrees before without using the latter command, and Git automatically prunes them for you, which is the entire reason why lock exists, to prevent Git from automatically doing this if you are putting working trees on removable devices that are unmounted while you are doing other work in the same git repo. You don’t need to use prune at all unless you don’t want to wait for Git to automatically do it for you. Yes, Git has a repair command for when you accidentally fuck things up by moving/removing working trees without using git worktree, but when comparing to Mercurial one has to ask, what happens if you move the share pool your shared Mercurial clones are based on? I doubt they automatically repair themselves, so what is the fix in that case? In any case, I’ve never had to run repair myself, and I imagine the Mercurial authors were like “who would do that?” and left it at that.

            Mercurial avoids most of the subcommands by storing the repo history in a separate location from the “original” repo, i.e. the share pool, and treating shared clones as siblings, but I think its arguable whether that’s actually better - though it is obviously simpler in some ways. I think it might make more sense to compare that setup to having multiple non-shared clones, since Mercurial appears to support each shared clone acting like a full clone. The obvious benefit is you don’t have to waste disk space on multiple copies of the same history, with the tradeoff that the clones can’t go anywhere without the share pool (e.g. by storing them on a removable disk for use across machines). It’s certainly nice to be able to treat each working tree as a full clone, but unless you are keeping those extra working trees around on a long-term basis, is that really an advantage over just creating working trees per-branch like git worktree does?

            Anyway, I don’t necessarily think either Mercurial or Git are better here, just different, and they each have their rationales for why they work the way they do. The only argument I’m really making is that Git’s UX here isn’t actually bad. There are plenty of places where Git’s UX suffers, but this is not one of them IMO.

      2. 1

        Note that worktree does not work with submodules. I so want to use it, but am blocked by that.