Posts in Git (20 found)

distributed identity

Sorry but the law doesn't care about your merkle trees We've all heard the horror stories of dealing with names and technology , and yet, we must persist. In this story, we journey through the thorny brambles of git commit history and life events, and ultimately manage to tame them using ATProto. Say that you have a big 'ol git repo. Thousands of commits, hundreds of issues, dozens of PRs. And now let's say one of your contributors—not a maintainer, mind you, just someone who helps out once in a while—is named Andrea P. Researcher <[email protected]>. Andrea gets a new job at Greenfield & Co and changes her email. She come to you with a request: actually, i didn't like my old job very much, could you update the commit history to the new email? Now you have a small problem: Git is a merkle tree that preserves all past commits in amber. You can't change any past commit without "force-pushing" to the default branch, invalidating every single commit hash, distributed checkout, and open PR. Not to worry, you say! The authors of git predicted this. You have the perfect tool: git mailmap . Andrea says perfect, perfect, and adds an entry: Now, Andrea gets married and changes her maiden name to Locksmith. She's still working at Greenfield & Co, though, so she has the same email. She comes back and asks: can you change the commits since I got married to Andrea Locksmith, but keep the old ones as Andrea Researcher? And you say, no, mailmap doesn't really work that way ... git identifies you by your (name, email) tuple, it doesn't have any concept of a date. She grumbles a bit, but well, it's not such a big deal. She uses mailmap to change her commits to consistently use Andrea Locksmith for all the changes (it's close enough) and leaves the ones be. Andrea meets some friends and goes to some movies and shows and reads some books and has a few revelations about himself. He comes back and says, hey i have some news, um, my new name is Bobby. Can you update all my commits? And you point him to mailmap and he says no no, that keeps my deadname around right at the top of the repo. Can't you change the actual data somehow? Look, man, this is important to me. 1 And you apologize, and you really do feel bad; but you look at the 300 open PRs, and the hard-coded commits in , and the merge tooling you wrote that can't handle force-pushes, and you just ... you just don't want to think about how much effort it would be to fix all those. And Bobby gets it, he does, and he makes a mailmap entry instead. But all the same, he contributes a bit less now. Bobby moves to Germany and learns they have this neat thing called GDPR . And one of his friends tells him, look man, you have a right to be called the name you chose, you know? An honest-to-god, enshrined-in-law legal right. And now Bobby comes back to you and say "I want you to rip my name out of the repository because it's personal data of an individual." Well, you're not quite sure that's how GDPR works (maybe you have a "legitimate interest"? are we really sure you were offering a "product or service" to Bobby?). But all the same, lawyers are expensive, and you'd rather not go through the hassle, especially since, well, Bobby really does have a good reason here. And anyway, it would be bad PR, and this isn't the thing you want to lose contributors over. So you figure out how to use and update and force-push to and write a blog post telling everyone how to rebase their PRs and realize you hard-coded commit hashes in your docs so you go back and fix those too and realize you hard-coded them even in some blog posts so now you have to update those and ugh. ok. that's probably most of it now. And Bobby is happy and you're happy he's happy and you put on a half-hearted smile. And then his mate Charlie comes by and says actually that was neat, can you do that for me too? Bobby asked for three things: Git can give us 1, but not 2 or 3. Git is making your life a right-old pain here! If this happens two or three more times, you might even be willing to switch to a different tool, one that supports this better. And—what's this?—there's something called ! It says this: The censor command instructs Mercurial to erase all content of a file at a given revision without updating the changeset hash. This allows existing history to remain valid while preventing future clones/pulls from receiving the erased data. Typical uses for censor are due to security or legal requirements, including: Perfect, perfect, except wait that said content of a file . The author of a commit is actually not the content of a file. It's metadata attached to the commit itself. Damn. So close. Well, how does work anyway? Censored nodes can interrupt mercurial's typical operation whenever the excised data needs to be materialized. Some commands, like hg cat/hg revert, simply fail when asked to produce censored data. Others, like hg verify and hg update, must be capable of tolerating censored data to continue to function in a meaningful way. Such commands only tolerate censored file revisions if they are allowed by the "censor.policy=ignore" config option. Oh. Uh. They're destroying the "cryptographic hashes" part of the merkle tree. That's fine? Probably? We don't really need to work. For complicated reasons related to "filelogs" , this doesn't let us get up to much mischief anyway; we can corrupt but not much more. If we extended this same scheme to metadata though, things would get worse, we might be able to corrupt itself to point to a malicious history. Does it need to work that way? Let's consider the properties we want by comparing to how changes usually work online: gets us 1, kinda. It's still traceable pretty easily. gets us 3. Nothing currently out there gets us 2 or 2 4. 4 is probably not something we care about too much here. "Delete all traces of this commit, even the fact it existed" doesn't seem particularly necessary. But better support for 1 and 2 would be very nice. I have good news for you: there is already an online identity service that does this! (No, it's not OpenID Connect.) It's called ATProto and it's the protocol powering Bluesky . Exactly how ATProto works is a bit out of scope for this post (for more on that see The Hitchhiker's Guide to the Atmosphere ), but what is relevant is how ATProto handles identity . It does this with a decentralized identifier (DID) . For example, my Bluesky handle is , but my ATProto DID 3 is . Because the two are different, that allowed me to change my handle from to when I first joined Bluesky 4 . What's interesting about this is it allows you to control where your data lives. ATProto has a concept of a Personal Data Server (PDS) : by default, when you join Bluesky, your data lives on their servers, but you can migrate your PDS and self-host your own data. This means, for example, that Bluesky can't ban you; you can always migrate to Blacksky 5 . Ok, so, let's put this together and use it in our Git identity alternative. We now have portability, modification, revocability, and—oh? what's that? a primary source? The full history of DID operations and updates, including timestamps, is permanently publicly accessible. This is true even after DID deactivation. It is important to recognize (and communicate to account holders) that any personally identifiable information (PII) encoded in alsoKnownAs URIs will be publicly visible even after DID deactivation, and can not be redacted or purged. In the context of atproto, this includes the full history of handle updates and PDS locations (URLs) over time. To be explicit, it does not include any other account metadata such as email addresses or IP addresses. Handle history could potentially de-anonymize account holders if they switch handles between a known identity and an anonymous or pseudonymous identity. Does it need to work this way? This is talking specifically about bluesky handles . But ATProto has a bunch of other kinds of data . We could just. You know. Build our own. With blackjack, and hookers. Here's an example of a custom ATPRoto record: This is a chess game played between me and , on checkmate.blue , a multiplayer chess app built fully-client side on top of ATProto. Unlike records, normal ATProto records have no permanent history and can be deleted. So, one way we could fix Bobby's problem is something like this: This gets us all the properties we want! One possible UI that could be built around this: Bobby is happy because from his perspective he just commits like normal, maybe with one extra if he wants to tie his identity to the repo immediately. The maintainer is happy because they NEVER EVER EVER have to think about GDPR for commits again. Bobby's ex is unhappy that he moved to Germany, but that's a different story. You could imagine an extension of this idea to commit bodies that allows building on the same mechanism, although it's more complicated because you probably want that to be under the control of the repo owner, not the person who originally submitted the change. Now, this doesn't solve literally every problem—archive.org is a thing—but it sure does solve "all people have to do to deanonymize you is run ". if this doesn't sound important to you, imagine that idk, Bobby is going into a witness protection program or something, or getting a divorce from an abusive ex. also, get the hell off my site. ↩ 522 came up with an alternate way to allow deletions and renames by having a mutable mailmap that's not on the main branch . This isn't quite as flexible as the proposal here, because it only allows the maintainer to change your identity, not you yourself, but it's much much simpler, and works today with normal config. ↩ technically DIDs aren't specific to ATProto , but they weren't widely used before Bluesky started using them. ↩ actually, tangled cheats and lets you write only your email in the commit, then looks for a ATProto DID with that email and uses that to find your Bluesky handle and display name. it also lets you use a DID directly rather than an email. wild shit. doesn't help with our goal of hiding names and emails though. ↩ they can ban you from Bluesky, but not from ATProto as a whole. see the creator of Blacksky's post about this for more information. ↩ you want this per-repo so that you can delete your association with one project without having to delete all of them. ↩ live fetches would be expensive, but you can make them cheaper with an appview , which you can think of as a giant cache with structured database-like queries. this is similar to the idea behind trustfall . ↩ If you want, you can imagine the public/private keypair to be a literal SSH key, which makes it work out of the box with most existing VCS'. This also lets you do fancy things with ssh-agent and SSH forwarding. ↩ real crypto, not that web3 bullshit. "crypto means cryptographers". ↩ Changing names and emails after the fact. Changing names after the fact, in a way that's time-based instead of identity-based. Changing names after the fact, in such a way that the previous name isn't detectable. Passwords, private keys, cryptographic material Licensed data/code/libraries for which the license has expired Personally Identifiable Information or other private data People can rename themselves and change their emails. People can delete their accounts. This usually shows up as a post by a user, or a username. People can (usually) delete the contents of their posts; sometimes admins retain edit history. People can (rarely) delete the post itself, in such a way that you can't distinguish "used to be a post here" from "never was a post here". Just build a new VCS data model from scratch. Look, if we make it a jj backend, it can't be that much work, right? holds a list of mumble mumble unique public key per repo , not a list of names/emails 6 . When you create a commit, instead of having a name/email pair in metadata, embed a private key signature of the commit. Create a new ATProto schema that has an optional current name and email, optional past emails, optional github link using OAuth, etc. Embed the public key and mumble mumble per-repo private key signature of the DID . When you run , it fetches your identity from ATProto. 7 You can edit any identity after the fact. You can add custom fields to the identity record that say to use certain names before or after a given date. You can delete your identity by removing the signature of the DID from your ATProto record. Because the signature is per-repo, deleting one signature doesn't affect the others. The mumble mumble asymmetric key pair make sure that only you can claim that DID corresponds to that commit. Probably. I'm not a cryptographer. Bobby runs , which gives him a private key he puts in 1password. The public key is automatically set up for him. Bobby, optionally, sets up commit signing. 8 If he doesn't set up signing, just embeds the public key as the identity. Bobby visits a website that has a pretty GUI setup for letting him edit his identity record. It can't exfiltrate his key because it runs fully client-side, which Bobby can test by turning off WiFi on his laptop, generating the new record (with only the signature, not the key), and then turning WiFi back on to copy-paste it into a fresh page of the app. Git preserves all data forever , in amber. Trying to change it is a goddamn nightmare. This is a problem for credentials, identities, and copyrighted material. makes a good-faith attempt to fix this, but only works for commit contents, not commit metadata This post proposes a way to fix this for identities, not just commit contents, using ATProto's distributed identities and personally-owned data storage, as well as a completely off-the-cuff unreviewed crypto 9 scheme. The scheme allows you to change your identity without having to rely on a second- or third-party. if this doesn't sound important to you, imagine that idk, Bobby is going into a witness protection program or something, or getting a divorce from an abusive ex. also, get the hell off my site. ↩ 522 came up with an alternate way to allow deletions and renames by having a mutable mailmap that's not on the main branch . This isn't quite as flexible as the proposal here, because it only allows the maintainer to change your identity, not you yourself, but it's much much simpler, and works today with normal config. ↩ technically DIDs aren't specific to ATProto , but they weren't widely used before Bluesky started using them. ↩ actually, tangled cheats and lets you write only your email in the commit, then looks for a ATProto DID with that email and uses that to find your Bluesky handle and display name. it also lets you use a DID directly rather than an email. wild shit. doesn't help with our goal of hiding names and emails though. ↩ they can ban you from Bluesky, but not from ATProto as a whole. see the creator of Blacksky's post about this for more information. ↩ you want this per-repo so that you can delete your association with one project without having to delete all of them. ↩ live fetches would be expensive, but you can make them cheaper with an appview , which you can think of as a giant cache with structured database-like queries. this is similar to the idea behind trustfall . ↩ If you want, you can imagine the public/private keypair to be a literal SSH key, which makes it work out of the box with most existing VCS'. This also lets you do fancy things with ssh-agent and SSH forwarding. ↩ real crypto, not that web3 bullshit. "crypto means cryptographers". ↩

0 views
マリウス 2 weeks ago

Teaching an Old Dog New Tricks: Forgejo + XMPP

I recently decided to leave Codeberg and run my own Forgejo instance instead. After finishing the initial setup, I realized that a forge I host myself is a forge that I get to shape, and that I no longer have to settle for whatever feature set someone else considered reasonable. I can now make it truly mine, and switch on the things I have always wanted a Git host to do. The first of those things concerns the registration. Right now Forgejo , like pretty much every other forge, wants an email address and sends a verification link to it, because apparently a clicked link is still considered a proof that a human is on the other end. I, however, would much rather let people sign up with an XMPP JID instead. Partly, because an email address you can receive a link at has long stopped being a real obstacle for automated spam, so the whole email verification is less beneficial than people assume, and partly because XMPP is the superior protocol, and I would prefer my users to arrive over it rather than over email to begin with. Unfortunately, Forgejo has no native XMPP support. Additionally, it also makes no use of Go ’s shared objects , which would let extensions be built largely independently of the core, and which for a monolith of this size would make a great deal of sense. There is no clean place to add a protocol to it without going into the core itself. I was not, however, looking to implement real JID registration and authentication into Forgejo . That would have been a fight against windmills, and I say that from experience. My past attempts at far lighter changes were killed off by bureaucratic requirements like design discussions and collecting of use cases , and probably a laissez-passer A38 somewhere along the way. So I did what any reasonable but slightly unhinged person would do, and decided to bridge Forgejo ’s existing email integration into XMPP instead. Switchyard is a small daemon that speaks SMTP on one side and XMPP on the other. Email and XMPP happen to share the same address form, , so the recipient maps across directly, and a message addressed to goes out as a chat to the JID . It accepts the mail a service submits over SMTP , turns each one into a job on a queue, and a worker delivers it over a standing XMPP connection that reconnects on its own when the server drops it. Because it listens for SMTP the way any mail server does, Forgejo needs no patch to hand its mail over. It gets configured as the endpoint with the same block you would point at a real mail server: On the other end, a short gives it the credentials it checks Forgejo against, a certificate for the submission port, and the single XMPP account it sends everything from: With that in place, every message Forgejo would otherwise send by email, like the sign-up confirmation, the password reset, or the note about a new issue, instead goes out over XMPP to whatever JID the user typed into the email field. Switchyard also handles IDNs, because, you know, that’s kind of my thing. :-) SMTP always carries the domain in its punycode form, while XMPP uses the native IDN , so it decodes the recipient domain on the way through, and a user who registers as in Forgejo is reached at on XMPP . This lightweight service is not specific to Forgejo . Switchyard works with anything that can be pointed at an SMTP server, and turns that software into something that speaks XMPP without the software itself knowing that anything changed. The one adjustment that should be made on the other side is relabeling the “Email address” field in the HTML templates to “JID” , so people know what to put there. In Forgejo ’s case that is a small template patch, which you can easily maintain yourself. It’s a very different commitment from maintaining a full XMPP implementation that upstream will most likely never accept. The downside is that this costs you email (for now!). Once Forgejo is talking to Switchyard , everything it sends goes to XMPP , and a user who did enter an email address gets nothing. I’m already working on fixing that, by having Switchyard first check whether the destination host is an actual XMPP server at all, and fall back to forwarding the message to an ordinary mail server that you configure when it is not. That way one protocol ( SMTP ) comes in, and is distributed to either the preferred one ( XMPP ) when available, or forwarded to the fallback ( SMTP ) when it isn’t. Easy-peasy lemon-squeezy. Is any of this clean and elegant engineering? Definitely not. It is an SMTP server wired to an XMPP client (with a job queue in between), it works, and it does the job. With Switchyard I get a feature that belongs upstream but almost certainly will never get there, and for me this counts more than a clean implementation that would maybe win the beauty contest, but would remain a patch for probably the rest of its life. A patch that I would have to continuously merge and maintain across Forgejo releases. Note: I’m still in the midst of thoroughly testing this implementation on tty.fail , which is my own Git platform, and I haven’t yet opened up registrations for others to sign up. In case you would like to contribute to any of the projects and therefor would like to get early access, join the community channel and let me know!

0 views
Lalit Maganti 4 weeks ago

GitHub has alternatives, but no replacement

Codeberg , a Git code hosting platform, recently took a decision to prohibit projects that mostly consist of generative-AI-written code which has prompted concern and extensive discussion elsewhere . The decision does not surprise me, and I don’t mean that as a criticism. Codeberg has always presented itself as a mission-driven alternative to GitHub, not neutral infrastructure. 1 What interests me is the disappointment in the response. Many people reacted as though one of the few plausible GitHub replacements had ruled them or their projects out. They wanted Codeberg to be a universal alternative, a better GitHub and the obvious place to go when leaving it. To me, that exposes a big gap in the open-source space. There are plenty of places to host a Git repository, but remarkably few places to host an open-source community. GitHub gives projects a shared pool of identities, habits and paths to discovery. None of the alternatives has reproduced that at a similar scale. I don’t think the answer has to be another centralized platform, or that every project should live in one place. But decentralization is not enough on its own. Whatever replaces GitHub still needs a shared social layer: identities contributors already have, conventions they understand and ways to discover projects across the network. Whenever dissatisfaction with GitHub comes up, someone inevitably says: “Git is decentralized. Just self-host a forge.” I’ve self-hosted Gitea for years, so this is an argument I’m very familiar with. Self-hosting works well for personal projects, but I wouldn’t use it for something I wanted strangers to contribute to. On GitHub, most people already have an account and understand how issues and pull requests work. On my forge, even reporting a small bug means creating another account, learning how my forge works and what conventions I want you to follow. Unless someone really cared, they probably wouldn’t bother. I know I wouldn’t. And contribution is only half of it. GitHub used to be genuinely good at discovery. I regularly found projects because someone I followed starred them, often in areas I would never have searched for myself. It felt like a social network built around people making things. GitHub has since redesigned that feed, and I almost never visit it anymore. Defaults are powerful: once discovery stopped being part of the experience GitHub put in front of me, it largely disappeared from my workflow. The basic experience of GitHub has been getting steadily worse. It is slow, things regularly fail to load and notifications are unreliable. GitHub itself recently described two major incidents as “not acceptable” . Its pull request experience has been awful too. Large PRs are painfully slow to navigate and review. Stacked PRs 2 have been common inside large software companies for well over a decade, but only just became a thing with GitHub and, even then, seems to be quite buggy . What frustrates me about GitHub’s push towards AI is that the core forge feels neglected while Copilot appears everywhere. An agent writing more code doesn’t help when the interface for reviewing it is already struggling. Ghostty exemplifies this frustration. In late April, Mitchell Hashimoto announced that Ghostty is leaving GitHub because frequent outages were preventing its maintainers from working reliably: On the day I am writing this post, I’ve been unable to do any PR review for ~2 hours because there is a GitHub Actions outage. This is no longer a place for serious work if it just blocks you out for hours per day, every day. Interestingly, he also makes the point that GitHub is more than hosting: To the “Git is distributed!” crowd: the issue isn’t Git, it’s the infrastructure we rely on around it: issues, PRs, Actions, etc. Hashimoto said Ghostty was in discussions with multiple commercial and FOSS providers and planned an incremental migration. The fact that such a prominent project had to shop around, rather than move to an obvious default, is exactly the gap I mean. It’s worth going through the alternatives and the problems I see with each: GitLab is capable, but it feels incredibly corporate, even more so than GitHub. 3 Nor have I found it as good as GitHub at helping people stumble across projects and developers. SourceHut is focused and transparent and, like Codeberg, openly values-driven. 4 Its email-oriented workflow, while battle-tested by projects like the Linux kernel, is unfamiliar to most GitHub users. Forgejo ’s federation project may eventually connect self-hosted instances into a shared network. It looks promising, but has been in development for quite some time, remains experimental 5 and is not yet a practical answer to the social fragmentation of self-hosting. Radicle is technologically interesting: repositories are replicated peer to peer, while issues and patches are stored alongside them. But it still feels too immature to replace GitHub for a public project. For example, its public web interface lets people browse repositories, but contributing requires them to install its CLI or desktop application. Someone encountering a bug should not have to install the forge’s software merely to report it. 6 The project I’m most interested in is Tangled , based mostly on gut feel. I like its focus on the social experience around code. For example, its home page immediately shows me a bunch of cool projects, exactly like old GitHub used to do. But it is still in alpha, and it remains to be seen whether it can blossom into a true alternative. One thing that stands out is that, apart from GitLab, every competitor is built around either decentralization or a social mission. That makes each of them fundamentally less “straightforward” than a for-profit company. It makes me wonder whether there is room for one. Perhaps it could look more like bunny.net than a venture-backed startup: a deliberately boring company with no ambition to become the operating system for software development or to reorganize programming around whatever technology investors currently find exciting. It might simply concentrate on making open-source collaboration pleasant and reliable, charge developers and smaller organizations directly, and grow at whatever pace that revenue supports. The open question is whether this could be a viable business. The difficult part is exactly what I keep saying is missing: shared identity, shared conventions and discovery only become valuable once a platform has reached scale. Better repository hosting alone would not solve that cold-start problem. 7 I don’t know who, if anyone, will solve it. But a real replacement will have to treat the social layer as the product, not as something that appears automatically once enough repositories are hosted. The English translation of the bylaws says Codeberg’s purpose “also intends to raise awareness for the social and philosophical questions” connected to free content and free software.  ↩︎ Stacked PRs split a large change into a sequence of dependent, individually reviewable changes.  ↩︎ For example, GitLab’s homepage describes it as “your intelligent orchestration platform for DevSecOps.”  ↩︎ For example, its Terms of Service prohibit cryptocurrency and blockchain projects.  ↩︎ Forgejo says federation is under active development and considered experimental . Moderation and access control have not yet been developed, and breaking changes are expected.  ↩︎ Radicle’s web interface is read-only , so interaction requires the CLI or desktop application. Features such as releases and bundled package registries are absent. CI is possible through community projects , but is neither official nor tightly integrated.  ↩︎ Bitbucket is the obvious existing counterexample: a conventional commercial forge that has never developed into a shared open-source community comparable to GitHub.  ↩︎ The English translation of the bylaws says Codeberg’s purpose “also intends to raise awareness for the social and philosophical questions” connected to free content and free software.  ↩︎ Stacked PRs split a large change into a sequence of dependent, individually reviewable changes.  ↩︎ For example, GitLab’s homepage describes it as “your intelligent orchestration platform for DevSecOps.”  ↩︎ For example, its Terms of Service prohibit cryptocurrency and blockchain projects.  ↩︎ Forgejo says federation is under active development and considered experimental . Moderation and access control have not yet been developed, and breaking changes are expected.  ↩︎ Radicle’s web interface is read-only , so interaction requires the CLI or desktop application. Features such as releases and bundled package registries are absent. CI is possible through community projects , but is neither official nor tightly integrated.  ↩︎ Bitbucket is the obvious existing counterexample: a conventional commercial forge that has never developed into a shared open-source community comparable to GitHub.  ↩︎

0 views
Kev Quirk 1 months ago

I've Moved Back to GitHub

Back in May, I wrote a post about how I'd migrated all my repos away from GitHub. This ended up being a combination of my self-hosted Git server on my Synology (for my private repos) and Codeberg for my public ones. Well, since then Codeberg has just given me problem after problem. I had issues working with repos via SSH for weeks . It just kept timing out, or taking an age to actually do anything. So I switched to HTTPS instead, which seemed a little better, albeit still generally very slow. This morning I came to do a bit of work on some issues and PRs that have been logged against the Pure Comments repo and (unsurprisingly at this point), I was greeted with this: I've been trying to get to the repo for around 30 minutes now, and it just keeps failing. I can't work like this - I'm a busy guy, so when I do get time to work on these fun side projects, my shit needs to work. People give GitHub a hard time for outages, scraping, etc. but in all the years I've used it, I've never had an issue. It has always been quick, and it always worked. In my boredom while waiting for Codeberg to sort themselves out, I decided to peruse my RSS feeds and I came across this post by Sal . It seems he's been having similar problems with Gitlab. This was the final straw. I've had enough of battling with Codeberg, so I've switched back to GitHub. Some may not like this decision as Codeberg is generally considered the more community friends code hub, but I need to make a pragmatic choice here as the constant issues are sapping most the fun out of these projects. I'll continue to host my private projects on the Synology, as that works great. But as of right now, we're back on GitHub for all [my projects . I think I've caught all references to Codeberg across the various sites and docs (thanks to Gemini), but if you see a problem, please log an issue. There is 1 final update on Codeberg for both Pure Blog and Pure Comments. This points the updater back to GitHub, from then on, subsequent releases will be on GitHub. I'm bound to get some comments and suggestions about other options other than moving back to GitHub, so I'll try and hit them before you take the time to comment/email with recommendations: What about a self-hosted Forgejo instance? Absolutely not. I don't have time to maintain something like that. I'd rather spend my free time working on fun projects than managing infrastructure. But GitHub are really bad because of what about ? Nope, sorry. I don't want to use other platforms that potentially introduce the same issues I've been having with Codeberg. Look at Sal's post further up - he was on Gitlab and having similar problems to me. This is disappointing, are you sure about your decision? I'm very sure. It is disappointing, I agree. But I've given it a lot of thought over the last few weeks and I'd rather go with the tool that I know will work, than the one I have to battle with. Plus, this is all public code, so I don't think I'm giving anything away by switching back. I'll NEVER use GitHub. I'm gonna stop using now. That's a shame, but it's your call. Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment . What about a self-hosted Forgejo instance? Absolutely not. I don't have time to maintain something like that. I'd rather spend my free time working on fun projects than managing infrastructure. But GitHub are really bad because of what about ? Nope, sorry. I don't want to use other platforms that potentially introduce the same issues I've been having with Codeberg. Look at Sal's post further up - he was on Gitlab and having similar problems to me. This is disappointing, are you sure about your decision? I'm very sure. It is disappointing, I agree. But I've given it a lot of thought over the last few weeks and I'd rather go with the tool that I know will work, than the one I have to battle with. Plus, this is all public code, so I don't think I'm giving anything away by switching back. I'll NEVER use GitHub. I'm gonna stop using now. That's a shame, but it's your call.

0 views
André Arko 1 months ago

jj sales pitch

A new jj tutorial, summarizing a many of the reasons jj is so useful, and powerful, in just a few paragraphs. jj improves upon Git by achieving two opposing goals at the same time: it has a simpler mental model and command set, but simultaneously provides more powerful functionality. jj simplifies Git not by hiding complexity, but by using a better conceptual model. – Evan’s Jujutsu Tutorial

0 views
Lalit Maganti 1 months ago

The git history command deserves more attention

Working with lots of changes in parallel on git can be painful. You end up juggling branches and commits, and running scary commands that can leave your tree in a half-broken state if you so much as sneeze. , an alternative to , gets discussed a lot these days ( 1 , 2 , 3 , 4 ) and is often pitched as a solution. While I’m very sold on the problems is trying to solve, the way it solves them hasn’t quite hit home with me. Every 3 months, for the last 1.5 years, I try it out for a few days, really trying to make it part of my workflow but eventually I give up and go back to git. 1 That’s where comes in. It’s an experimental command that arrived across two releases, 2.54 (April, and subcommands) and 2.55 (June, subcommand). It got a flurry of attention on each release day, and then, as far as I can tell, not much community discussion since. Which is a shame, because IMO it already delivers several of the benefits people tout for without needing to switch your whole workflow. And the cool thing is that it’s part of the core git distribution, so you can try it without installing anything.

0 views
André Arko 2 months ago

<code>jj</code>'s whole deal

jj’s whole deal is that it collapses many Git concepts (stashes, staging, fixups, in-progress rebases, conflicts) into a single unified model of working with history, which then lets you use the same tools to do all of those things. For example, to fix up an old commit you jump to it, edit it, and jump back to where you were; to fix a rebase conflict you jump to the conflicting commit, edit it, and jump back to where you were, using the same commands. – Understanding Jujutsu bookmarks This is such a good summary, I’m probably going to steal it when I try to explain in the future.

0 views

Superpowers 5.1.0

I'm pleased to announce Superpowers 5.1.0. The biggest changes in this release are: -A reworking of how we use git worktrees, now that Claude Code and Codex have first-class support for them. You can always find the latest version of Superpowers on GitHub Removing the deprecated slash commands that date to the earliest days of Superpowers, when skills triggering was still really brittle. Switching the 'code reviewer' subagent to be a regular subagent with a custom prompt, reducing our platform footprint, so that we no longer need any special subagent types. A more efficient OpenCode integraiton New support for Factory Droid Cleanups to Subagent Driven Development and Code Review Removal of some old boilerplate text in skills that didn't improve outcomes.

0 views
matduggan.com 4 months ago

If I Could Make My Own GitHub

My friend and I have a game where we talk about what we'd do if we were rich. Not rich like 'paid off the mortgage' rich. Rich like a man who owns a submarine he's never been inside. Rich like a man whose third wife has a skincare line. Tech-titan rich — the kind of money that buys you a compound in Wyoming and the confidence to wear the same gray t-shirt to congressional testimony." One of mine, for a long time, has been the dream of making a new forge. I was prompted to write this after reading the good post about Ghostty leaving GitHub but it's something I've written and talked about for a few years. Given how bad GitHub has become at its core job, it seemed like a fun opportunity to try and write up what my billionaire folly of a forge would look like. This folly would have less penile rockets filled with aging celebrities. GitHub, GitLab and Gitea (those being the 3 I've used the most) are all modeled on effectively the same design. There are differences, but you can tell that GitHub sets the pattern for the industry and then those features are ported over to the other two with varying levels of success. The issue with all of these is that they're designed to add things git doesn't do that you need. Git is great at what it is designed to do, but what it is designed to do isn't the way most people are using it. Git is a perfect tool for kernel development. It is a decentralized distributed version control system that relies on the idea of patches being sent to maintainers over email. You trust those maintainers to maintain their sections and merge in the stuff that makes sense and not merge in the other stuff. It's a pretty high trust environment that places very few restrictions on how online a specific contributor is or what system they are using. If you have a laptop from 2010 that connects to the Internet once a week you can still be a meaningful contributor to a project with these workflows. . However, in most jobs, git is effectively just the way I pull and push from a centralized repository stored in a forge. All the important stuff happens inside the forge, and very little of it happens on my client. Pull Requests are how I enforce the four-eyes principle, GitHub Actions are how I run my tests and linting on those Pull Requests to ensure they are functional and meet my organizational requirements, the user's identity in relation to that forge is how I verify who they are. I track issues with my code through Issues and cut releases for users to download through Releases. There's not a lot of git in this workflow, this is mostly placed on top of git. So here are the primary issues I see with modern forges that I would love to solve. Absolutely. There are a lot of tools that do parts of this. I want someone to take them, put them all together and fit them up. I want JJ as the VCS, I want this as the forge and I want the expectation that I as a user could live happily with a raspberry pi as a forge for a long time. I want those forges designed around modern concepts like object storage and shallow clones and getting constantly hammered by LLM bots. Now in a universe where GitHub was doing a good job, I wouldn't even bother writing this up. GitHub is the default and talking to people about overcoming the default is usually a waste of time. Heinz is the default ketchup, when I order a Coke I don't want a Pepsi and if I'm going to use a forge up until 2026 there would have to be an amazing reason for me to not choose the one that everyone uses. Up until recently other forges have been like sweet potato french fries, which is to say never the thing you actually want. But we live in a world where the monolithic forge is breaking down and nobody has built the replacement. The people with the money are busy with the rockets. The people with the taste are busy with their day jobs. And the rest of us are opening PRs titled 'asdfasdf' at midnight, waiting for a robot to check them, wondering when the tool we spend our whole working lives inside stopped being built for us. If I ever get the submarine money, I'll let you know. Stuff happens in the wrong order. You know the PR. Commit 1: 'Feature.' Commit 2: 'fix.' Commit 3: 'fix.' Commit 4: 'actually fix.' Commit 5: 'please.' Commit 6, made at 11:47 PM on a Thursday: 'asdfasdf'. This person has a family. This person has hobbies. This person is, at this moment, crying. You don't want the feedback loop after the commit you want it before. Let me do an enforced pre-commit hook to run the jobs remotely on the forge and provide the feedback to the user before they push. PR approval is too boolean. The PR is approved or it's not approved. Real code review, like real life, lives in the middle. 'Sure, fine, we'll deal with it later' is a legitimate human response and should be a legitimate button. Gerrit has a better model for this. If I weakly approve something as a maintainer, let me flag it for later. PRs are too inflexible. I don't need 4 eyes on every change, especially in a universe where LLMs exist. The global GDP lost annually to senior engineers staring at a four-line PR waiting for someone — anyone — to type 'LGTM' could fund a moon mission. A nice one. With legroom. Let me customize and more easily control this. If the person is a maintainer and the LLM says its low risk/no risk just let them go. Stacked PRs are just better. They're easier to review and understand. They have to be a first-class citizen not an add-on through a tool other than your VCS. A forge shouldn't do everything. Issue tracking yes. Kanban board, probably not. Wiki? I doubt it. Everything tools always turn into crap. You add features when its easy to add features and then pay the maintenance price for those features forever regardless of their rate of adoption because now someone, somewhere uses them and you are locked in. The standard unit of hosting is too large. Running Github Enterprise is a big task. Running GitLab is also a relatively big ask. These are complicated products with a lot of moving pieces. I want smaller individual units of hosting that I can link together to make an organization. It's fine if they're not globally federated and I need to make an account for each Organization, but an Organization should be flexible enough to let me say "these 12 Raspberry Pis are my org". I don't know how they communicate securely, I hire people for that problem. My local copy of the repo should be a representation of the entire repo, not just the code. I should be able to approve a PR from the same VCS I use to check in the code. I should be able to go through my issues by looking through local files. On the flip side, since I need to be online all the time to really work with a team, don't make me pay the storage price all the time. I want my VCS and my forge to work together. If I clone a repo, I want a pretty limited history for that repo when I clone. If I start to go back in time, spin up a worker to go fetch that stuff from the VCS when I need it. I don't need to hammer the forge with giant clone requests on the assumption that I might need to rebuild the forge at any moment with the entire history of the entire project. Actions need to be signed, SHA'd and usable offline. If I want, I should be able to get tarballs of all my actions, stick them in the repo and tell my system "don't go anywhere for checkout action, that's right here". If I say latest, have that work like Dependabot does now where it opens up a PR to put the latest tarballs in my repo. Actions are critical and they should be runnable on my local machine through the same VCS if I want to.

0 views
David Bushell 4 months ago

GitHub is sinking

TL;DR: GitHub used to be cool and now it’s a lame slop graveyard. GitHub is racing towards the mythical zero nines of uptime. Users are starting to notice that GitHub is now a Microsoft product. Eww! Official uptime paints a concerning chart. The missing status page tell a far worse story. Whatever the truth, it’s impossible to miss the delightful experience that is Microsoft GitHub if you use it semi-regularly. Microsoft acquired GitHub and applied their unique brand of enshittification. Amongst their achievements was the spawning of the Copilot circle of hell . Now they’re effectively DDoSing themselves with slop . I won’t dwell on what else went wrong. I don’t know and I don’t care. GitHub is impressively bad now. It’s embarrassing. Shameful. As I write this the obituaries are flooding in: It’s long past time to get off this sinking ship! GitHub has become synonymous with “source control” and I worry too many users don’t know that Git is not GitHub. The core technology of Git is open source. It’s distributed, meaning that all repositories are equal. Git works without a centralised service. Such a practice is a construct of social convenience. GitHub was a useful add-on. Microsoft has turned GitHub into an expensive liability. Network effects are hard to topple but if anyone can do it, Microsoft can. GitHub’s fake star economy is worthless. GitHub is inundated with bots and drowning in slop and doing everything to encourage it. Microsoft is turning GitHub into the Moltbook of code, it ain’t for you and me anymore. Your CI pipeline is over-engineered and GitHub Actions are an abomination (see: [1] [2] ). Finding another solution is an absolute chore but do you trust GitHub to be reliable? Look, the ship is sinking! Sure, the water looks freezing. Don’t hang around and allow Microsoft to pull you under. You don’t need to move everything in one go. Start the process. The nearest lifeboat to escape GitHub is another centralised Git forge. Just sign up and push your repo to the new upstream. Some services can automate the migration and maybe even import issues. Personally I’d leave issues behind in a tragic boating accident. Codeberg — a non-profit and community-led project with an established track record. This is the safe alternative that’ll stick around. It’s the flagship instance of Forgejo . Tangled — an alpha stage start-up with interesting AT protocol integration. Worth considering for smaller solo projects. Seems cool. Gitea — they offer cloud managed Git hosting. It’s the original open source project that Codeberg/Forgejo forked away from. GitLab — enterprise grade, meaning it’s bloated and confusing but it’ll impress your boss. This could be the choice if you need multiple meetings to make the choice. Bitbucket — trade one soul destroying corpo fun vacuum for another. Strongly discouraged, but Bitbucket does technically fit the anything but GitHub category. If you’re cool like me , you or your organisation can self-host a Git forge with actions and releases . My recommendation is Forgejo . There is talk of federation between Forgejo instances but it’s not happening anytime soon. If you want open collaboration push a copy to Codeberg. Gitea and GitLab also have self-hosted options. Be aware, GitLab is a comparative chonker. When I said “Git is not GitHub” the same applies to other forges. Do you need those add-ons? Nothing is stopping you from raw-doggin’ Git over SSH: How you manage collaboration is another question. If Linux can be maintained by sending patches to an email mailing list, “doesn’t work at scale” arguments are skill issues. But seriously, a centralised Git forge is a decent compromise in my opinion. Maybe they collapse like GitHub in future. Always have an exit plan. Just use anything but GitHub. Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds. Ditching GitHub - Lonami Ghostty Is Leaving GitHub - Mitchell Hashimoto Before GitHub - by Armin Ronacher From GitHub to Codeberg/Forgejo - Jonas Hietala

0 views
Unsung 4 months ago

“The cheatsheet you won’t need.”

A fun bit of storytelling on the website for a git client Retcon : I don’t have personal experience with Retcon. I definitely struggled a lot with git’s syntax over the years, and have my own cheatsheet that looks similar to this. But what I really liked from this page was the elevation of undo to be the North Star. I think it’s very, very well deserved. To the best of my knowledge, undo in its modern form arrived in 1983 with Apple Lisa – Byte magazine called it a “ tremendous security blanket ” – and then over the next decade or so blossomed into its current state: an infinite, multi-level, lightning-fast safety hatch that works pretty much everywhere, always there in the bottom-left corner of your keyboard, so second-nature you might not even realize you’re invoking it. In early apps, before undo arrived, you had to be very careful about what you did and when you saved your work. Later on, undo worked on just one level, so you had to think a lot about how to spend it before things became irreversible. Today, undo just works . It truly became Back Space: The Next Generation. But any user-facing “just works” hand wave means a lot of people’s hard and invisible work behind the scenes. So if you’re reading this, and at some point in your career you worked on making undo better, my tip of the hat to you (and send me a message!). #errors #interface design #maintenance

0 views
iDiallo 5 months ago

Shower Thought: Git Teleportation

In many sci-fi shows, spaceships have a teleportation mechanism on board. They can teleport from inside their ship to somewhere on a planet. This way, the ship can remain in orbit while its crew explores the surface. But then people started asking: how does the teleportation device actually work? When a subject stands on the device and activates it, does it disassemble all the atoms of the person and reconstruct them at the destination? Or does it scan the person, kill them, and then replicate them at the destination? This debate has been on going for as long as I can remember. Since teleportation machines exist only in fiction, we can never get a true answer. Only the one that resonates the most. So, that's why I thought of Diff Teleportation. Basically, this is a Git workflow applied to teleportation. When you step onto a device, we run the command: Then, the machine will have to suspend activity on the master branch. This will make merging the branch much simpler in the future. Now, the person that has been teleported can explore the planet and go about mission 123. While they are doing their job, let's see what flags are supported in : When the mission is completed, they can be teleported back. Well, not the whole person, otherwise we end up with a clone. We could analyze the new data and remove any unwanted additions. For example, we could clean up any contamination at this point. But for the sake of time, I'll explore that another day. As an exercise, run for your own curiosity. For now, all we are interested in is the information that the teleportee has gathered from the planet, which we will merge back into master. I imagine in science fiction, there is an automated way for PR reviews that is more reliable than an LLM. Once that process is completed, we can merge to master and run some cleanup code in the build pipeline. Somewhere down on planet XYZ, a clone stepped onto the teleportation device. He saw a beam of light scan his body from head to toe. Then, for a moment, he wondered if the teleportation had worked. But right before he stepped off, the command ran, and he was pulverized. Back in the spaceship, a brand-new clone named appeared at the teleportation station. He was quickly sanitized, diff'd, and reviewed. But before he could gather his thoughts, the command ran, and he was pulverized. Not a second later, the original subject was reanimated, with brand-new information about "his" exploration on planet XYZ. Teleportation is an achievable technology. We just have to come to terms with the fact that at least two clones are killed for every successful teleportation session. In fact, if we are a bit more daring, we might not even need to suspend the first subject. We can create multiple clones, or agents, and have them all explore different things. When their task is complete, we can wrestle a bit with merge conflict, run a couple commands, and the original subject is blessed with new knowledge. OK, I'm getting out of this shower.

4 views
Susam Pal 5 months ago

Git Checkout, Reset and Restore

I have always used the and commands to reset my working tree or index but since Git 2.23 there has been a command available for these purposes. In this post, I record how some of the 'older' commands I use map to the new ones. Well, the new commands aren't exactly new since Git 2.23 was released in 2019, so this post is perhaps six years too late. Even so, I want to write this down for future reference. It is worth noting that the old and new commands are not always equivalent. I'll talk more about this briefly as we discuss the commands. However, they can be used to perform similar tasks. Some of these tasks are discussed below. To experiment quickly, we first create an example Git repository. Now we make changes to the files and stage some of the changes. We then add more unstaged changes to one of the staged files. At this point, the working tree and index look like this: File has staged changes. File has both staged and unstaged changes. File has only unstaged changes. File is a new staged file. In each experiment below, we will work with this setup. All results discussed in this post were obtained using Git 2.47.3 on Debian 13.2 (Trixie). As a reminder, we will always use the following command between experiments to ensure that we restore the experimental setup each time: To discard the changes in the working tree and reset the files in the working tree from the index, I typically run: However, the modern way to do this is to use the following command: Both commands leave the working tree and the index in the following state: Both commands operate only on the working tree. They do not alter the index. Therefore the staged changes remain intact in the index. Another common situation is when we have staged some changes but want to unstage them. First, we restore the experimental setup: I normally run the following command to do so: The modern way to do this is: Both commands leave the working tree and the index in the following state: The ( ) option tells to operate on the index (not the working tree) and reset the index entries for the specified files to match the version in . The unstaged changes remain intact as modified files in the working tree. With the option, no changes are made to the working tree. From the arguments we can see that the old and new commands are not exactly equivalent. Without any arguments, the command resets the entire index to , so all staged changes become unstaged. Similarly, when we run without specifying a commit, branch or tag using the ( ) option, it defaults to resetting the index from . The at the end ensures that all paths under the current directory are affected. When we run the command at the top-level directory of the repository, all paths are affected and the entire index gets reset. As a result, both the old and the new commands accomplish the same result. Once again, we restore the experimental setup. This time we not only want to unstage the changes but also discard the changes in the working tree. In other words, we want to reset both the working tree and the index from . This is a dangerous operation because any uncommitted changes discarded in this manner cannot be restored using Git. The modern way to do this is: The working tree is now clean: The ( ) option makes the command operate on the working tree. The ( ) option resets the index as described in the previous section. As a result, this command unstages any changes and discards any modifications in the working tree. Note that when neither of these options is specified, is implied by default. That's why the bare command in the previous section discards the changes in the working tree. The following table summarises how the three pairs of commands discussed above affect the working tree and the index, assuming the commands are run at the top-level directory of a repository. The command is meant to provide a clearer interface for resetting the working tree and the index. I still use the older commands out of habit. Perhaps I will adopt the new ones in another six years, but at least I have the mapping written down now. Read on website | #technology | #how-to Experimental Setup Reset the Working Tree Reset the Index Reset the Working Tree and Index

0 views
matduggan.com 6 months ago

GitButler CLI Is Really Good

My workflow has remained mostly the same for over a decade. I write everything in Vim using the configuration found here . I run Vim from inside of tmux with a configuration found here . I write things on a git branch, made with the CLI, then I add them with to that branch, trying to run all of the possible linting and tests with before I waste my time on GitHub Actions. Then I run which is an alias to . Finally I successfully commit, then I copy paste the URL returned by GitHub to open a PR. Then I merge the PR and run to go back to the primary branch, which is an alias to . This workflow, I think, is pretty familiar for anyone working with GitHub a lot. Now you'll notice I'm not saying because almost nothing I'm doing has anything to do with . There's no advantage to my repo being local to my machine, because everything I need to actually merge and deploy code lives on GitHub. The CI runs there, the approval process runs there, the monitoring of the CI happens there, the injection of secrets happens there. If GitHub is down my local repo does, effectively, nothing. My source of truth is always remote, which means I pay the price for complexity locally but I don't benefit from it. At most jobs: This means the following is also true: Almost all the features of are wasted on me in this flow. Now because this tool serves a million purposes and is designed to operate in a way that almost nobody uses it for, we all pay the complexity price of and never reap any of the benefits. So instead I keep having to add more aliases to paper over the shortcomings of . These are all the aliases I use at least once a week. Git's offline-first design creates friction for online-first workflows, and GitButler CLI eliminates that friction by being honest about how we actually work. (Edit: I forgot to add this disclaimer. I am not, nor have ever been an employee/investor/best friends with anyone from GitButler. They don't care that I've written this and I didn't communicate with anyone from that team before I wrote this.) So let's take the most basic command as an example. This is my flow that I do 2-3 times a day without my aliases. I do this because can't make assumptions about the state of the world. However because GitButler is designed with the assumption that I'm working online, we can skip a lot of this nonsense. It's status command understands that there is always a remote main that I care about and that when I run a status that I need to understand my status relative to the remote main as it exists right now. Not how it existed the last time I remembered to pull. However this is far from the best trick it has up its sleeve. You're working on a feature, notice an unrelated bug, and now you have to stash, checkout, fix, commit, push, checkout back, stash pop. Context switching is expensive and error-prone. GitButler effectively hacks a solution into that fixes this with multiple branches applied simultaneously. Assign files to different branches without leaving your workspace. What do I mean by that. Let's start again with my status Great looks good. Alright so lets say I make 2 new branches. I'm working on a new feature for adding auth and while I'm working on that, I see a typo I need to fix in a YAML. I can work on both things at the same time: And easily commit to both at the same time without doing anything weird . Stacked PRs are the "right" way to break up large changes so people on your team don't throw up at being asked to review 2000 lines, but Git makes them miserable. When the base branch gets feedback, you have to rebase every dependent branch, resolve conflicts, force-push, and pray. Git doesn't understand branch dependencies. It treats every branch as independent, so you have to manually maintain the stack. GitButler solves this problem with First-class stacked branches. The dependency is explicit, and updates propagate automatically. So what do I mean. Let's say I make a new API endpoint in some Django app. First I make the branch. So let's say I'm working on the branch and get some good feedback on my PR. It's easy to resolve the comments there while leaving my branched off this as a stacked thing that understands the relationship back to the first branch as shown here. In practice this is just a much nicer way of dealing with a super common workflow. Maybe the most requested feature from new users I encounter is an easier undo. When you mess up in Git, recovery means diving into , understanding the cryptic output, and hoping you pick the right . One wrong move and you've made it worse. GitButlers is just easier to use. So the basic undo functionality is super simple to understand. rolls me back one operation. To me the mental model of a snapshot makes a lot more sense than the git history model. I do an action, I want to undo that action. This is better than the git option of: I've been using GitButler in my daily work since I got the email that the CLI was available and I've really loved it. I'm a huge fan of what this team is doing to effectively remodel and simplify Git operations in a world where almost nobody is using it in the way the tool was originally imagined to be used. I strongly encourage folks go check it out for free at: https://docs.gitbutler.com/cli-guides/cli-tutorial/tutorial-overview . It does a ton of things (like help you manage PRs) that I didn't even touch on here. Let me know if you find something cool that I forgot at: https://c.im/@matdevdug You can't merge without GitHub (PRs are the merge mechanism) You can't deploy without GitHub (Actions is the deployment trigger) You can't get approval without GitHub (code review lives there) Your commits are essentially "drafts" until they exist on GitHub You never work disconnected intentionally You don't use local branches as long-lived divergent histories You don't merge locally between branches (GitHub PRs handle this) You don't use for archaeology — you use GitHub's blame/history UI (I often use git log personally but I have determined I'm in the minority on this). Your local repo might be offline for days or weeks The "remote" might be someone else's laptop, not a central server Divergent histories are expected and merging is a deliberate, considered act

0 views
Luke Hsiao 10 months ago

First impressions of jj from a git fan

Jujutsu ( ) is a distributed version control system (like , , , , etc.). However, it is rather unique in that it is -compatible—it uses as a storage layer, meaning you can use it right now on your existing repos without disrupting anyone else. It also provides other features, such as It also clearly has a (currently small but) passionate group of users, which is a good sign of a useful tool. Some of these users strongly dislike , finding its user interface unintuitive and clunky. I, however, am very much not part of that group. I love . Over the years, I’ve curated a configuration that I find a joy to use. I’ve built up years’ worth of muscle memory. As a result, even though I love investing in interfaces , such as new software tools, I haven’t ever felt motivated enough to try . That changed this past week when I learned that Steve Klabnik found so interesting that he’s leaving Oxide to pursue it further . As an experiment, I’ve committed to using only for at least a couple of weeks. As a user, the tutorial that clicked best for me is Steve’s . So, if you’re a fan, I suggest starting there. What I miss from I quickly ran into two features that I wish were supported. First, does not support submodules . Sure, submodules are not great in many cases. However, one very common case I use them for is Zola themes, such as the theme for this blog. My customization lives in my repository, and the theme for the site lives in a submodule. However, it looks like they are working on that actively and with great care. Second, I missed . This one is more minor, because I can always just fall back to to do so. Even in just a few days, I’ve wanted to serialize a patch to a file (i.e., ) or apply some patch I have sitting around with . One example is a patch file I keep around that customizes an environment for debugging purposes. As a -native approach, I’ve been keeping a change separate that I can rebase into my patch series and move out later if needed, but that feels clunkier. Then, there are a handful of more rarely used things that I have not needed yet, and consequently have not figured out equivalents for. For example, I’m not sure about an equivalent for view release notes via tags. With , I used an alias like But, then it seems right now doesn’t support anything but simply listing tags right now. No creation or deletion. Ouch. I’m not sure an equivalent for . Most likely would need to build some template. I don’t have some equivalent of . But again, it’s -compatible, so I can just keep using . I’m sure there will be a long tail of things like this. Steve and others have described as “both simpler and easier than git, but at the same time, it is more powerful.” I’m not sure I fully agree. In some ways, it has better defaults (e.g., , ), but in other ways, it is complex (e.g., revset language). That said, I do see how it is more powerful. You cannot exactly your mental model to directly onto ’s. That said, one of the main things I enjoy at $WORK is that for a repository, ’s default “view” (for lack of a better word) is a branch, whereas ’s default view is much higher level. I find ’s approach more intuitive: it’s always pretty trivial to see what branches/PRs I have ongoing at a glance (i.e., with ). It also means you can easily do fun stuff like rebase all of your branches at the same time, and push them all simultaneously to your remote! This is made even better by the fact that conflicts are first-class objects, meaning you can rebase all your branches, and deal with conflicts later. In a similar vein: emphasizes commits or changes in a way I find pleasing. Even though in I have ways to do similar things, does indeed make them easier. For example, splitting one commit into multiple is easier. In , you’d probably do something like In , just does the more intuitive thing. Instead, the same process looks more like As another example, I’m a huge fan of . It makes folding follow-up changes into an appropriate earlier commit easy and effective. However, it isn’t built into . Meanwhile, ships with , which does the same thing. I found that in , I was already following a workflow close to the squash workflow recommended by ’s creator, Martin. The main thing I still feel clumsy with is named bookmarks (similar to branches). Specifically, I find it hard to remember to the bookmark when I make updates, and how to rebase them without looking up commands. That said, it’s becoming more familiar over time. Much like , I’ve also found that ’s defaults aren’t entirely sufficient for me. I needed to make some tweaks. You can see my up-to-date dotfiles here , but as a snapshot, these are changes I found to be particularly valuable right from the start. First, I strongly prefer over the default viewer. also makes it easy to view with another tool with . For example, I also like using . I added a config from this discussion so doesn’t clear the screen on quit. Second, I like having my conventional commit template automatically populated on . I also have the diff included , which mimics the config I had with using Third, I changed the default bookmark prefix to . Finally, I added a bunch of aliases I use frequently. I use to copy-paste commit descriptions into PR descriptions. I use to view all the logs most relevant to me. This was discovered courtesy of Will Richardson . I use to view all logs. I use and to push all my bookmarks, and rebase all my changes on main, respectively. Finally, I use to move my bookmark to the latest change (I found this one thanks to Shaddy ). I actually find quite fun! Again, I do not think it “solved” any pain points I had with . I was already a very happy user. Ultimately, ’s interface and mental model do feel refreshing enough that I’m likely to continue using it. conflicts as a first-class object no explicit index revset language operation log and powerful undo automatic rebase and conflict resolution

0 views
Maurycy 10 months ago

You already have a git server:

If you have a git repository on a server with ssh access, you can just clone it: You can then work on it locally and push your changes back to the origin server. By default, git won’t let you push to the branch that is currently checked out, but this is easy to change: This is a great way to sync code between multiple computers or to work on server-side files without laggy typing or manual copying. If you want to publish your code, just point your web server at the git repo: … although you will have to run this command server-side to make it cloneable: That’s a lot of work, so let’s set up a hook to do that automatically: Git hooks are just shell scripts, so they can do things like running a static site generator: This is how I’ve been doing this blog for a while now: It’s very nice to be able to type up posts locally (no network lag), and then push them to the server and have the rest handled automatically. It’s also backed up by default: If the server breaks, I’ve still got the copy on my laptop, and if my laptop breaks, I can download everything from the server. Git’s version tracking also prevents accidental deletions, and if something breaks, it’s easy to figure out what caused it.

0 views
André Arko 10 months ago

<code>jj</code> part 4: configuration

Just like git, jj offers tiers of configuration that layer on top of one another. Every setting can be set for a single repo, for the current user, or globally for the entire system. Just like git, jj offers the ability to create aliases, either as shortcuts or by building up existing commands and options into new completely new commands. Completely unlike git, jj also allows configuring revset aliases and default templates, extending or replacing built-in functionality. Let’s look at the ways it’s possible to customize jj via configurations. We’ll cover basic config, custom revsets, custom templates, and custom command aliases. Let’s start with some configuration basics. You can globally configure jj to change your name and email based on a path prefix, so you don’t have to remember to set your work email separately in each work repo anymore. Or perhaps you want jj to wait for your editor if you are writing a commit message, but you don’t want jj to wait for your editor to exist if you are editing your jj configuration file. You can ensure that using a scope. I also highly recommend trying out multiple options for formatting your diffs, so you can find the one that is most helpful to you. A very popular diff formatter is , which provides syntax aware diffs for many languages. I personally use , and the configuration to format diffs with delta looks like this: Another very impactful configuration is which tool jj uses to handle interactive diff editing, such as in the or commands. While the default terminal UI is pretty good, make sure to also try out Meld, an open source GUI. In addition to changing the diff editor, you can also change the merge editor, which is the program that is used to resolve conflicts. Meld can again be a good option, as well as any of several other merging tools. Tools like mergiraf provide a way to attempt syntax-aware automated conflict resolution before handing off any remaining conflicts to a human to resolve. That approach can dramatically reduce the amount of time you spend manually handling conflicts. You might even want to try FileMerge, the macOS developer tools built-in merge tool. It supports both interactive diff editing and conflict resolution. Just two more configurations before we move on to templates. First, the default subcommand, which controls what gets run if you just type and hit return. The default is to run , but my own personal obsessive twitch is to run constantly, and so I have changed my default subcommand to , like so: The last significant configuration is the default revset used by . Depending on your work patterns, the multi-page history of commits in your current repo might not be helpful to you. In that case, you can change the default revset shown by the log command to one that’s more helpful. My own default revset shows only one change from my origin. If I want to see more than the newest change from my origin I use to get the longer log, using the original default revset. I’ll show that off later. While the jj template docs are a great reference, they don’t do very much to show off what’s possible by using templates, so we’ll show some examples. The first and most obvious template is the template, which controls how each change is rendered in the log output. The default template is named , and jj comes with a few pre-built template options for the log view, like and . You can see them all by running . Use to try them out and see how they look. If you want to experiment with your own custom log formats, you can provide a template string instead of the name of an existing template. Here’s an example inline template that prints out just the short change ID, a newline, and then the change description: Try out the various documented template properties yourself! Once you’re happy with a template that you’ve tested, you can add it to your config with a name, and then use it by name. Here’s a more complicated example, adapted from @marchyman in the jj Discord, with several of the elements that we’ve discussed so far. This example changes the default command, adding extra options. It also uses a named revset alias, and a named template alias. The named template recreates the regular template from , and uses a revset filter to include the list of changed files in changes that are both mutable (that is, not yet pushed) and also within 3 commits of the end of a branch. By showing up to 7 changes, but the file list for up to 3 mutable changes, the log output becomes more useful, reminding you what files have been changed in the most recent commits that you might want to push. One more template you might want to adjust is the default description, shown when running or for a change that does not yet have a description. If you don’t use a VCS GUI, it can be helpful to see the diff of what is being committed at the same time as you write the commit message. In git, that meant running , but in jj that means adjusting the default description. The jj config docs provide an example template that will replicate that effect, and show you the diff while you write the message. Building on the earlier section where we talked about , creating your own revset aliases is a powerful way to construct views tailored to your personal needs. A built-in revset alias we can use to illustrate this is . In the same way that git requires to push over an existing remote commit, jj requires to edit a commit matched by . (Incidentally, I believe this arrangement is also an example of the way jj’s design is an improvement on git. Instead of deciding to overwrite published commits during a push, you are forced to decide much earlier, during the edit itself, if you are okay with changing a published commit. Anyway, back to revset aliases.) The default revset is , which composes four other revsets together. Let’s look at each one. The revset is simply the primary branch, whether it is named or , wrapped in to remove it if none of those branches exist. The revset is every change that has been given a tag. The revset is exactly what it sounds like: any branch provided by the remote that you have not manually opted in to tracking locally (which is what you would do if you are working on the branch). All three revsets are combined into one overall list with the operator. Those heads are then used to construct the full list of immutable commits, which is every ancestor of those heads. Now that you know how that works, we can change it. For example, perhaps you want the same immutability rules that provides, where commits are immutable once they have been pushed to any remote at all. In that case, you could add this to your config file: With that configuration, jj will extrapolate that it cannot change any commits on the primary branch, all the commits leading up to a tag, and all commits leading up to a named branch in the remote. If you use this revset, jj will stop you from changing commits once you have pushed them to a branch, since you told it to make those immutable. With this power at your disposal, you can change the default revset shown when you run , or you can create your own named revsets for your own purposes. You can see my revset aliases in my dotfiles, and read more about the default aliases in the jj docs. Okay. Now we can talk about command aliases. First up, the venerable . In the simplest possible form, it takes the closest bookmark, and moves that bookmark to , the parent of the current commit. What if you want it to be smarter, though? It could find the closest bookmark, and then move it to the closest pushable commit, whether that commit was , or , or . For that, you can create a revset for , and then tug from the closest bookmark to the closest pushable, like this: Now your bookmark jumps up to the change that you can actually push, by excluding immutable, empty, or descriptionless commits. What if you wanted to allow tug to take arguments, for those times when two bookmarks are on the same change, or when you actually want to tug a different bookmark than the closest one? That’s also pretty easy, by adding a second variant of the tug command that takes an argument: This version of tug works just like the previous one if no argument is given. But if you do pass an argument, it will move the bookmark with the name that you passed instead of the closest one. How about if you’ve just pushed to GitHub, and you want to create a pull request from that pushed bookmark? The command isn’t smart enough to figure that out automatically, but you can tell it which bookmark to use: Just grab the list of bookmarks attached to the closest bookmark, take the first one, pass it to , and you’re all set. What if you just want single commands that let you work against a git remote, with defaults tuned for automatic tugging, pushing, and tracking? I’ve also got you covered. Use to colocate jj into this git repo, and then track any branches from upstream, like you would get from a git clone. Then, you can to find the closest bookmark to , do a git fetch, rebase your current local commits on top of whatever just got pulled, and then show your new stack. When you’re done, just . This push handles looking for a tuggable bookmark, tugging it, doing a git push, and making sure that you’re tracking the origin copy of whatever you just pushed, in case you created a new branch. For another perspective on jj configuration, partly overlapping with this post, check out my JJ Con talk, stupid jj tricks . You can also try reading some jj config files directly, like my jj config , or thoughtpolice’s jj config , or pksunkara’s jj config . Hopefully this tour through jj configuration options has revealed some ways that jj can be used to do more than was possible with only git. Next time, we’ll focus on the ways that jj goes beyond git, offering things that were impractical or even impossible before.

0 views