Latest Posts (20 found)
André Arko 1 months ago

We want to move Ruby forward

On September 9, without warning, Ruby Central kicked out the maintainers who have cared for Bundler and RubyGems for over a decade. Ruby Central made these changes against the established project policies , while ignoring all objections from the maintainers’ team . At the time, Ruby Central claimed these changes were “temporary". However, While we know that Ruby Central had no right to act the way they did, it is nevertheless clear to us that the Ruby community will be better off if the codebase, maintenance, and legal rights to RubyGems and Bundler are all together in the same place. To bring this about, we are prepared to transfer our interests in RubyGems and Bundler to Matz , end the dispute over the GitHub enterprise account, 2 GitHub organizations, and 70 repositories, and hand over all rights in the Bundler logo and Bundler name, including the trademark applications in the US, EU, and Japan. Once we have entered into a legal agreement to settle any legal claims with Ruby Central and transfer all rights to Matz, the former maintainers will step back entirely from the RubyGems and Bundler projects, leaving them fully and completely to Matz, and by extension to the entire Ruby community. Although Ruby Central’s actions were not legitimate, our commitment to the Ruby community remains strong. We’re choosing to focus our energy on projects to improve Ruby for everyone, including rv , Ruby Butler , jim , and gem.coop . Signed, The former maintainers: André , David , Ellen , Josef , Martin , and Samuel None of the “temporary” changes made by Ruby Central have been undone, more than six weeks later. Ruby Central still has not communicated with the removed maintainers about restoring any permissions. Ruby Central still has not offered “operator agreements” or “contributor agreements” to any of the removed maintainers. The Ruby Together merger agreement plainly states that it is the maintainers who will decide what is best for their projects, not Ruby Central. Last week, Matz stepped in to assume control of RubyGems and Bundler himself. His announcement states that the Ruby core team will assume control and responsibility for the primary RubyGems and Bundler GitHub repository. Ruby Central did not communicate with any removed maintainers before transferring control of the rubygems/rubygems GitHub repo to the Ruby core team. On October 24th, Shan publicly confirmed she does not believe the maintainers need to be told why they were removed .

0 views
André Arko 1 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
André Arko 1 months ago

Announcing <code>rv</code> 0.2

With the help of many new contributors, and after many late nights wrestling with make, we are happy to (slightly belatedly) announce the 0.2 release of rv ! This version dramatically expands support for Rubies, shells, and architectures. Rubies: we have added Ruby 3.3, as well as re-compiled all Ruby 3.3 and 3.4 versions with YJIT. On Linux, YJIT increases our glibc minimum version to 2.35 or higher. That means most distro releases from 2022 or later should work, but please let us know if you run into any problems. Shells: we have added support for bash, fish, and nushell in addition to zsh. Architectures: we have added Ruby compiled for macOS on x86, in addition to Apple Silicon, and added Ruby compiled for Linux on ARM, in addition to x86. Special thanks to newest member of the maintainers’ team @adamchalmers for improving code and tests, adding code coverage and fuzzing, heroic amounts of issue triage, and nushell support. Additional thanks are due to all the new contributors in version 0.2, including @Thomascountz , @lgarron , @coezbek , and @renatolond . To upgrade, run , or check the release notes for other options.

0 views
André Arko 1 months ago

The RubyGems “security incident”

Ruby Central posted an extremely concerning “ Incident Response Timeline ” today, in which they make a number of exaggerated or purely misleading claims. Here’s my effort to set the record straight. First, and most importantly: I was a primary operator of RubyGems.org, securely and successfully, for over ten years. Ruby Central does not accuse me of any harms or damages in their post, in fact stating “we have no evidence to indicate that any RubyGems.org data was copied or retained by unauthorized parties, including Mr. Arko.” The actions I took during a time of great confusion and uncertainty (created by Ruby Central!) were careful, specific, and aimed to defend both Ruby Central the organization and RubyGems.org the service from potential threats. The majority of the team, including developers in the middle of paid full-time work for Ruby Central, had just had all of their permissions on GitHub revoked. And then restored six days later. And then revoked again the next day. Even after the second mass-deletion of team permissions, Marty Haught sent an email to the team within minutes, at 12:47pm PDT, saying he was (direct quote) “terribly sorry” and “I messed up”. Update : Added email timestamp. The erratic and contradictory communication supplied by Marty Haught, and the complete silence from Shan and the board, made it impossible to tell exactly who had been authorized to take what actions. As this situation occurred, I was the primary on-call. My contractual, paid responsibility to Ruby Central was to defend the RubyGems.org service against potential threats. Marty’s final email clearly stated “I’ll follow up more on this and engage with the governance rfc in good faith.”. Just a few minutes after that email, at 1:01pm PDT, Marty also posted a public GitHub comment , where he agreed to participate in the proposed governance process and stated “I’m committed to find the right governance model that works for us all. More to come.” Update : screenshot of comment removed and replaced with link, since the comment appears to still be visible (at least to logged out users) on GitHub. Given Marty’s claims, the sudden permission deletions made no sense. Worried about the possibility of hacked accounts or some sort of social engineering, I took action as the primary on-call engineer to lock down the AWS account and prevent any actions by possible attackers. I did not change the email addresses on any accounts, leaving them all owned by a team-shared email at rubycentral.org, to ensure the organization retained overall control of the accounts, even if individuals were somehow taking unauthorized actions. Within a couple of days, Ruby Central made an (unsigned) public statement, and various board members agreed to talk directly to maintainers. At that point, I realized that what I thought might have been a malicious takeover was both legitimate and deliberate, and Marty would never “fix the permissions structure”, or “follow up more” as he said. Once I understood the situation, I backed off to let Ruby Central take care of their “security audit”. I left all accounts in a state where they could recover access. I did not alter, or try to alter, anything in the Ruby Central systems or GitHub repository after that. I was confident, at the time, that Ruby Central’s security experts would quickly remove all outside access. My confidence was sorely misplaced. Almost two weeks later, someone asked if I still had access and I discovered (to my great alarm), that Ruby Central’s “security audit” had failed. Ruby Central also had not removed me as an “owner” of the Ruby Central GitHub Organization. They also had not rotated any of the credentials shared across the operational team using the RubyGems 1Password account. I believe Ruby Central confused themselves into thinking the “Ruby Central” 1Password account was used by operators, and they did revoke my access there. However, that 1Password account was not used by the open source team of RubyGems.org service operators. Instead, we used the “RubyGems” 1Password account, which was full of operational credentials. Ruby Central did not remove me from the “RubyGems” 1Password account, even as of today. Aware that I needed to disclose this surprising access, but also aware that it was impossible for anyone except former operators to exploit this security failure, I immediately wrote an email to Ruby Central to disclose the problem. Here is a copy of my disclosure email, in full. Ruby Central did not reply to this email for over three days. When they finally did reply, they seem to have developed some sort of theory that I was interested in “access to PII”, which is entirely false. I have no interest in any PII, commercially or otherwise . As my private email published by Ruby Central demonstrates, my entire proposal was based solely on company-level information, with no information about individuals included in any way. Here’s their response, over three days later. In addition to ignoring the (huge) question of how Ruby Central failed to secure their AWS Root credentials for almost two weeks, and appearing to only be aware of it because I reported it to them , their reply also failed to ask whether any other shared credentials might still be valid. There were more. Unbeknownst to me, while I was answering Marty’s email in good faith, Ruby Central’s attorney was sending my lawyer a letter alleging I had committed a federal crime, on the theory that I had “hacked” Ruby Central’s AWS account. On the contrary, my actions were taken in defense of the service that Ruby Central was paying me to support and defend. With my side of the story told, I’ll leave it to you to decide whether you think it’s true that “Ruby Central remains committed to transparent, responsible stewardship of the RubyGems infrastructure and to maintaining the security and trust that the Ruby ecosystem depends on.”

1 views
André Arko 1 months ago

Announcing gem.coop, a community gem server

The team behind the last ten years of rubygems.org, including @deivid-rodriguez, @duckinator, @martinemde, @segiddins, @simi, and myself, is very pleased to announce a new gem server for the Ruby community: gem.coop . The new server’s governance policies are being prepared in coordination with Mike McQuaid of Homebrew, and will be released later this week. The current versions of RubyGems and Bundler work with this new server already, and any Ruby developer is welcome to switch to using this new server immediately. We have exciting plans to add new features and functionality in the coming days. Join us!

1 views
André Arko 2 months ago

stupid jj tricks

This post was originally given as a talk for JJ Con . The slides are also available. Welcome to “stupid jj tricks”. Today, I’ll be taking you on a tour through many different jj configurations that I have collected while scouring the internet. Some of what I’ll show is original research or construction created by me personally, but a lot of these things are sourced from blog post, gists, GitHub issues, Reddit posts, Discord messages, and more. To kick things off, let me introduce myself. My name is André Arko, and I’m probably best known for spending the last 15 years maintaining the Ruby language dependency manager, Bundler. In the world, though, my claim to fame is completely different: Steve Klabnik once lived in my apartment for about a year, so I’m definitely an authority on everything about . Thanks in advance for putting into the official tutorial that whatever I say here is now authoritative and how things should be done by everyone using , Steve. The first jj tricks that I’d like to quickly cover are some of the most basic, just to make sure that we’re all on the same page before we move on to more complicated stuff. To start with, did you know that you can globally configure jj to change your name and email based on a path prefix? You don’t have to remember to set your work email separately in each work repo anymore. 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. Okay, enough of plain configuration. Now let’s talk about templates! Templates make it possible to do many, many things with jj that were not originally planned or built in, and I think that’s beautiful. First, if you haven’t tried this yet, please do yourself a favor and go try every builtin jj template style for the command. You can list them all with , and you can try them each out with . If you find a builtin log style that you especially like, maybe you should set it as your default template style and skip the rest of this section. For the rest of you sickos, let’s see some more options. The first thing that I want to show you all is the draft commit description. When you run , this is the template that gets generated and sent to your editor for you to complete. Since I am the kind of person who always sets git commit to verbose mode, I wanted to keep being able to see the diff of what I was committing in my editor when using jj. Here’s what that looks like: If you’re not already familiar with the jj template functions, this uses to combine strings, to choose the first value that isn’t empty, to add before+after if the middle isn’t empty, and to make sure the diff status is fully aligned. With this template, you get a preview of the diff you are committing directly inside your editor, underneath the commit message you are writing. Now let’s look at the overridable subtemplates. The default templates are made of many repeated pieces, including IDs, timestamps, ascii art symbols to show the commit graph visually, and more. Each of those pieces can be overrides, giving you custom formats without having to change the default template that you use. For example, if you are a UTC sicko, you can change all timestamps to render in UTC like , with this configuration: Or alternatively, you can force all timestamps to print out in full, like (which is similar to the default, but includes the time zone) by returning just the timestamp itself: And finally you can set all timestamps to show a “relative” distance, like , rather than a direct timestamp: Another interesting example of a template fragment is supplied by on GitHub, who changes the node icon specifically to show which commits might be pushed on the next command. This override of the template returns a hollow diamond if the change meets some pushable criteria, and otherwise returns the , which is the regular icon. It’s not a fragment, but I once spent a good two hours trying to figure out how to get a template to render just a commit message body, without the “title” line at the top. Searching through all of the built-in jj templates finally revealed the secret to me, which is a template function named . With that knowledge, it becomes possible to write a template that returns only the body of a commit message: We first extract the title line, remove that from the front, and then trim any whitespace from the start of the string, leaving just the description body. Finally, I’d like to briefly look at the possibility of machine-readable templates. Attempting to produce JSON from a jj template string can be somewhat fraught, since it’s hard to tell if there are quotes or newlines inside any particular value that would need to be escaped for a JSON object to be valid when it is printed. Fortunately, about 6 months ago, jj merged an function, which makes it possible to generate valid JSON with a little bit of template trickery. For example, we could create a output of a JSON stream document including one JSON object per commit, with a template like this one: This template produces valid JSON that can then be read and processed by other tools, looks like this. Templates have vast possibilities that have not yet been touched on, and I encourage you to investigate and experiment yourself. Now let’s look at some revsets. The biggest source of revset aliases that I have seen online is from @thoughtpolice’s jjconfig gist, but I will consolidate across several different config files here to demonstrate some options. The first group of revsets roughly corresponds to “who made it”, and composes well with other revsets in the future. For example, it’s common to see a type alias, and a type alias to let the current user easily identify any commits that they were either author or committer on, even if they used multiple different email addresses. Another group uses description prefixes to identify commits that have some property, like WIP or “private”. It’s then possible to use these in other revsets to exclude these commits, or even to configure jj to refuse to push them. Thoughtpolice seems to have invented the idea of a , which is a group of commits on top of some parent: Building on top of the stack, it’s possible to construct a set of commits that are “open”, meaning any stack reachable from the current commit or other commits authored by the user. By setting the stack value to 1, nothing from trunk or other remote commits is included, so every open commit is mutable, and could be changed or pushed. Finally, building on top of the open revset, it’s possible to define a “ready” revset that is every open change that isn’t a child of wip or private change: It’s also possible to create a revset of “interesting” commits by using the opposite kind of logic, as in this chain of revsets composed by . You take remote commits and tags, then subtract those from our own commits, and then show anything that is either local-only, tracking the remote, or close to the current commit. Now let’s talk about jj commands. You probably think I mean creating jj commands by writing our own aliases, but I don’t! That’s the next section. This section is about the jj commands that it took me weeks or months to realize existed, and understand how powerful they are. First up: . When I first read about absorb, I thought it was the exact inverse of squash, allowing you to choose a diff that you would bring into the current commit rather than eject out of the current commit. That is wildly wrong, and so I want to make sure that no one else falls victim to this misconception. The absorb command iterates over every diff in the current commit, finds the previous commit that changed those lines, and squashes just that section of the diff back to that commit. So if you make changes in four places, impacting four previous commits, you can to squash all four sections back into all four commits with no further input whatsoever. Then, . If you’re taking advantage of jj’s amazing ability to not need branches, and just making commits and squashing bits around as needed until you have each diff combined into one change per thing you need to submit… you can break out the entire chain of separate changes into one commit on top of trunk for each one by just running and letting jj do all the work for you. Last command, and most recent one: . You can use fix to run a linter or formatter on every commit in your history before you push, making sure both that you won’t have any failures and that you won’t have any conflicts if you try to reorder any of the commits later. To configure the fix command, add a tool and a glob in your config file, like this: Now you can just and know that all of your commits are possible to reorder without causing linter fix conflicts. It’s great. 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 huggable 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. Last, but definitely most stupid, I want to show off a few combo tricks that manage to deliver some things I think are genuinely useful, but in a sort of cursed way. First, we have counting commits. In git, you can pass an option to log that simply returns a number rather than a log output. Since jj doesn’t have anything like that, I was forced to build my own when I wanted my shell prompt to show how many commits beyond trunk I had committed locally. In the end, I landed on a template consisting of a single character per commit, which I then counted with . That’s the best anyone on GitHub could come up with, too . See? I warned you it was stupid. Next, via on Discord, I present: except for the closest three commits it also shows at the same time. Simply create a new template that copies the regular log template, while inserting a single conditional line that adds if the current commit is inside your new revset that covers the newest 3 commits. Easy. And now you know how to create the alias I promised to explain earlier. Last, but definitely most stupid, I have ported my previous melding of and over to , as the subcommand , which I alias to because it’s inspired by , the shell cd fuzzy matcher with the command . This means you can to see a list of local bookmarks, or to see a list of all bookmarks including remote branches. Then, you can to do a fuzzy match on , and execute . Jump to work on top of any named commit trivially by typing a few characters from its name. I would love to also talk about all the stupid shell prompt tricks that I was forced to develop while setting up a zsh prompt that includes lots of useful jj information without slowing down prompt rendering, but I’m already out of time. Instead, I will refer you to my blog post about a jj prompt for powerlevel10k , and you can spend another 30 minutes going down that rabbit hole whenever you want. Finally, I want to thank some people. Most of all, I want to thank everyone who has worked on creating jj, because it is so good. I also want to thank everyone who has posted their configurations online, inspiring this talk. All the people whose names I was able to find in my notes include @martinvonz, @thoughtpolice, @pksunkara, @scott2000, @avamsi, @simonmichael, and @sunshowers. If I missed you, I am very sorry, and I am still very grateful that you posted your configuration. Last, I need to thank @steveklabnik and @endsofthreads for being jj-pilled enough that I finally tried it out and ended up here as a result. Thank you so much, to all of you.

2 views
André Arko 2 months ago

<code>jj</code> part 1: what is it

I’ve been working on a blog post about migrating to jj for two months now. Rather than finish my ultimate opus and smother all of you in eight thousand words, I finally realized I could ship incrementally and post as I finish each section. Here’s part 1: what is jj and how do I start using it? Sure, you can do that. Convert an existing git repo with or clone a repo with . Work in the repo like usual, but with no needed, changes are staged automatically. Commit with , mark what you want to push with , and then push it with . If you make any additional changes to that branch, update the branch tip by running again before each push. Get changes from the remote with with . Set up a local copy of a remote branch with . Check out a branch with , and then loop back up to the start of the previous paragraph for commit and push. That’s probably all you need to get started, so good luck and have fun! Still here? Cool, let’s talk about how jj is different from git. There’s a list of differences from git in the jj docs, but more than specific differences, I found it helpful to think of jj as like git, but every change in the repo creates a commit. Edit a file? There’s a commit before the edit and after the edit. Run a jj command? There’s a commit before the command and after the command. Some really interesting effects fall out of storing every action as a commit, like no more staging, trivial undo, committed conflicts , and change IDs. When edits are always immediately committed, you don’t need a staging area, or to manually move files into the staging area. It’s just a commit, and you can edit it by editing the files on disk directly. Any jj command you run can be fully rewound, because any command creates a new operation commit in the op log. No matter how many commits you just revised in that rebase, you can perfectly restore their previous state by running . Any merge conflict is stored in the commit itself. A rebase conflict doesn’t stop the rebase—your rebase is already done, and now has some commits with conflicts inside them. Conflicts are simply commits with conflict markers, and you can fix them whenever you want. You can even rebase a branch full of conflicts without resolving them! They’re just commits. (Albeit with conflict markers inside them.) Ironically, every action being a commit also leads away from commits: how do you talk about a commit both before and after you amended it? You add change IDs. Changes give you a single identifier for your intention, even as you need many commits to track how you amended, rebased, and then merged those changes. Once you’ve internalized a model where every state is a commit, and change IDs stick around through amending commits, you can do some wild shenanigans that used to be quite hard with git. Five separate PRs open but you want to work with all of them at once? Easy. Have one commit that needs to be split into five different new commits across five branches? Also easy. One other genius concept jj offers is revsets . In essence, revsets are a query language for selecting changes, based on name, message, metadata, parents, children, or several other options. Being able to select lists of changes easily is a huge improvement, especially for commands like log or rebase. For more about jj’s design, concepts, and why they are interesting, check out the blog posts jj strategy , What I’ve Learned From JJ , jj init , and jj is great for the wrong reason . For a quick reference you can refer to later, there’s a single page summary in the jj cheat sheet PDF . Keep an eye out for the next part of this series in the next few days. We’ll talk about commands in jj, and exactly how they are both different and better than git commands.

2 views
André Arko 2 months ago

Bundler belongs to the Ruby community

I’ve spent 15 years of my life working on Bundler. When I introduce myself, people say “oh, the Bundler guy?”, and I am forced to agree. I didn’t come up with the original idea for Bundler (that was Yehuda). I also didn’t work on the first six months worth of prototypes. That was all Carl and Yehuda together, back when “Carlhuda” was a super-prolific author of Ruby libraries, including most of the work to modularize Rails for version 3. I joined the team at a pivotal moment, in February 2010, as the 0.9 prototype was starting to be re-written yet another time into the shape that would finally be released as 1.0. By the time Carl, Yehuda, and I released version 1.0 together in August 2010, we had fully established the structure and commands that Bundler 2.7.2 still uses today. I gave my first conference talk about Bundler at Red Dirt Ruby in May 2010. Because they would be too busy with Rails 3 talks, Yehuda and Carl asked me to give the first RailsConf talk about Bundler, in June 2010. As Carl and Yehuda drifted off to other projects, in 2011 and 2012 respectively, I took on a larger role, co-maintaining the project with Terence Lee, then on the Ruby platform team at Heroku. We shipped (and, embarrassingly, broke) many versions of Bundler on our way to the 1.1 release and its major speed improvements. We also gave several conference talks together, sharing what we had learned about Bundler, about gems, and about maintaining open source. In 2013, I managed to convince the owner of to sell me his domain, and rebuilt the website to host a separate copy of the documentation for every version of Bundler, ensuring even users on old versions could still access accurate documentation. By the end of 2013, Terence had drifted away from the project as well, and I realized that everyone using Ruby was now one bus (or one lottery ticket) away from Bundler having no significant maintainers. During 2014, I made sure to settle any remaining ownership issues, including purchasing the rights to the Bundler logo, and began investigating various funding ideas. I tried specialized consulting, corporate sponsorships, and asking Ruby Central about sponsoring Bundler and RubyGems development work. Ruby Central declined, citing their desire to stay focused on conferences, but suggested that if I wanted to pursue something myself they would be happy to collaborate. In 2015, I founded Ruby Together specifically to raise funds to pay the existing maintainers team of Bundler, RubyGems, and RubyGems.org. Over time, we were able to raise enough money to quietly but scrappily keep the entire RubyGems ecosystem maintained and functional. Ruby Together did not ever, at any point, demand any form of governance or control over the existing open source projects. Maintainers did their thing in the RubyGems and Bundler GitHub orgs, while Ruby Together staff and board members did their thing in the rubytogether GitHub org. By 2021, when Ruby Central and Ruby Together were both interested in merging together, funds were harder to find. Ruby Together had a membership program. Ruby Central wanted a to have a membership program. The confusing split between “Ruby Central owns the AWS account, but Ruby Together pays all the devs” continued to be a problem. We prepared a merger agreement (which you can read in full at the link), stating that Ruby Central’s new goal after the merger would be “paying maintainers to do the programming”. The agreement also states that Ruby Central will follow Ruby Together’s Vision, Mission, and Values , a document that is hosted in the rubycentral GitHub organization today. That document includes a very specific list of goals, including: You can read much more in both the merger agreement and in the Mission, Vision, and Values document, but the fundamental goal for both the non-profit and the open source projects is clear: this is all for the Ruby community. Without the community, there is no point to this work, and there is no way it could ever have been done in the first place. Without the 354 individuals who contributed to Bundler and to RubyGems, I could never have become “the Bundler guy” in the first place. In the last few weeks, Ruby Central has suddenly asserted that they alone own Bundler. That simply isn’t true. In order to defend the reputation of the team of maintainers who have given so much time and energy to the project, I have registered my existing trademark on the Bundler project. Trademarks do not affect copyright, which stays with the original contributors unchanged. Trademarks do not affect license terms, which stay MIT and unchanged. Trademarks only impact one thing: who is allowed say that what they make is named “Bundler”. Ruby Central is welcome to the code, just like everyone else. They are not welcome to the project name that the Bundler maintainers have painstakingly created over the last 15 years. While the trademark has been registered under my name as an individual, I will not keep it for myself, because the idea of Bundler belongs to the Ruby community. Once there is a Ruby organization that is accountable to the maintainers, and accountable to the community, with openly and democratically elected board members, I commit to transfer my trademark to that organization. I will not license the trademark, and will instead transfer ownership entirely. Bundler should belong to the community, and I want to make sure that is true for as long as Bundler exists.

0 views
André Arko 2 months ago

Adventures in CPU contention

Recently on this blog, I wrote about in-memory filesystems in Rust , and concluded that I wasn’t able to detect a difference between any form of in-memory filesystem and using a regular SSD on macOS. I also asked anyone who found a counterexample to please let me know. Last week, David Barsky of ERSC sent me an extremely compelling counter-example, and I spent several days running benchmarks to understand it better. The top level summary is that the test suite for the jj VCS exhibits an absolutely huge difference between running on an SSD and running against a ramdisk. In my first reproduction attempt, I found the SSD took 239 seconds, while the ramdisk took just 37 seconds. That’s bananas! How was that even possible? What I discovered will amaze, distress, and astound you. Probably. First, the context. The jj project recently shipped a change to always use when persisting a temporary file. My understanding is that this change was made to prevent certain kinds of bad data being written.

0 views
André Arko 2 months ago

Goodbye, RubyGems

As chronicled by my teammate Ellen, the RubyGems team is no more . I wish the best of luck to everyone taking on the herculean task of keeping package management functional and working for the entire Ruby community. In the meantime, I’m looking forward to spending my new free time focusing on projects that I’m truly excited about, like . We’d love to have your help as we work to build next-generation tools for Ruby.

0 views
André Arko 2 months ago

Rails on SQLite: exciting new ways to cause outages

This post was originally given as a talk for Friendly.rb . The slides are also available. Between Litestack and the Rails 8 trifecta of Solid Cable, Solid Cache, and Solid Queue, it’s easier than ever to spin up a Rails app that doesn’t need a database service, or a redis service, or a file storage service. It’s great to simplify things, but even after 20 years of deploying Rails apps I was still caught out by some of the ways things are different. Based on what happened when I built a new side project in Rails on SQLite, we’ll cover what’s different, what’s new, and several ways that you can knock your site offline or even destroy your entire production database. As we go, we’ll also talk about the advantages of using SQLite, and how those differences can help you. So who am I, how did I learn these things, and why should you listen to me? I’m André Arko, better known on the internet as @indirect. A long time ago, I helped create Bundler , and I’ve been the OSS team lead for RubyGems and Bundler for more than a decade at this point. I work at Spinel Cooperative , a collective of Ruby open source maintainers building rv , the Ruby language manager that can install Ruby in one second flat. We offer retainers for unlimited access to core team experts from Bundler, Rails, Hotwire, and more, who can answer your questions and solve your problems.

0 views
André Arko 3 months ago

<code>rv</code>, a new kind of Ruby management tool

For the last ten years or so of working on Bundler, I’ve had a wish rattling around: I want a better dependency manager. It doesn’t just manage your gems, it manages your ruby versions, too. It doesn’t just manage your ruby versions, it installs pre-compiled rubies so you don’t have to wait for ruby to compile from source every time. And more than all of that, it makes it completely trivial to run any script or tool written in ruby, even if that script or tool needs a different ruby than your application does. During all those years of daydreaming, I’ve been hoping someone else would build this tool and I could just use it. Then I discovered that someone did build it… but for Python. It’s called , and almost exactly one year ago version 0.3 shipped with all the features I had wished for, and even a few more that I hadn’t thought to wish for. Originally created as an alternative to , , and all the other Python dependency managers, grew to encompass several existing tools, and has a few completely new tricks up its sleeve. At this point, I’ve been using for almost a year and I have to say, it is really, really good. The combination of speed, reliability, and functionality creates a spectacularly good experience. No more changing a package as you install something new only to realize later you broke something old, no more setting up dependencies manually only to have the cronned script break later. About a month ago, I decided that if there was no tool like this for Ruby, I would make one rather than keep dreaming about it. I want to bring all the tricks and innovations of , , and into a tool for Ruby: .

0 views
André Arko 3 months ago

In-memory Filesystems in Rust

I’ve been working on a CLI tool recently, and one of the things it does is manage files on disk. I have written a lot of file management tests for Bundler , and the two biggest reasons that the Bundler test suite is slow are and . Knowing that, I thought I would try to get out ahead of the slow file stat problem by using an in-memory filesystem for testing. A collaborator mentioned being happy with the Go package named Afero for this purpose, and so I set off to look for a Rust equivalent to Afero. Conceptually, I was hoping to be able to replace with and swap out the backend in tests for something that’s completely in-memory so I don’t have to spend time waiting for syscalls. Unfortunately, based on my searching, not only is there nothing like Afero, but simply asking about it gets you a lecture about how such things aren’t necessary in Rust . Somewhat frustrated, I continued searching and eventually found a few options to try. First, I discovered the vfs crate, whose documentation seems pretty promising. It’s possible to swap out vfs backends to get a real filesystem, an in-memory filesystem, a new filesystem scoped into a directory, or files embedded in an executable. It’s actively maintained, and seems to have a decent number of current users. Unfortunately, as I got further along, it became apparent that the vfs crate isn’t actually a viable alternative to interacting directly with the filesystem. The vfs crate doesn’t have any support for symlinks, so resolving symlinks means going back to after all, and having to write special-cased symlink resolution code that doesn’t run if the filesystem is vfs. The real killer, though, was that vfs doesn’t contain any support for the concept of file permissions. Because of that, it’s impossible to write executables, which is core functionality for my tool.

0 views
André Arko 5 months ago

You should delete tests

We’ve had decades of thought leadership around testing, especially coming from holistic development philosophies like Agile, TDD, and BDD. After all that time and several supposedly superseding movements, the developers I talk to seem to have developed a folk wisdom around tests. That consensus seems to boil down to simple but mostly helpful axioms, like “include tests for your changes” and “write a new test when you fix a bug to prevent regressions”. Unfortunately, one of those consensus beliefs seems to be “it is blasphemy to delete a test”, and that belief is not just wrong but actively harmful. Let’s talk about why you should delete tests. To know why we should delete tests, let’s start with why we write tests in the first place. Why do we write tests? At the surface level, it’s to see if our program works the way we expect. But that doesn’t explain why we would write automated tests rather than simply run our program and observe if it works. If you’ve ever tried to work on a project with no tests, I’m sure you’ve experienced the sinking sensation of backing yourself into a corner over time. The longer the project runs, the worse it gets, and eventually every possible change includes stressfully wondering if you broke something, wondering what you missed, and frantically deploying fix after revert after fix after revert as fast as possible because each frantic fix broke something else.

0 views
André Arko 5 months ago

jj config edit

Today I stumbled across a jj cheat sheet and it contained an absolute gem of a command that I had somehow completely avoided knowing about this entire time: . You can just run a jj command to open the config file in your editor! You don’t have to remember whether you put it in , or , or whatever, you can just edit it! Super great.

0 views
André Arko 5 months ago

a jj prompt for powerlevel10k

I’m in the process of switching from git to jj right now. That switch is another post of its own, which I am still working on, but in the meantime I wanted to write up the way that I’ve set up my shell prompt to include information about the current jj repo. If you’re not already familiar with jj, you might find the ways jj is different from git helpful background reading for the rest of this post. I use the default macOS shell, zsh, and I don’t put much information into my prompt: basically just the current directory and the current git branch name, colored to show if I’m ahead or behind the remote. Using jj adds some interesting caveats to this kind of prompt, since jj branches aren’t named, and jj bookmarks don’t move when you create new commits. In order to keep the same kind of “where am I in this repo” information available in my prompt, I decided to show the name of the closest bookmark, as well as how many commits ahead or behind that bookmark I currently am. Conceptually, that changes my git prompt of to a jj prompt of , until I move the bookmark up so I can push it to the git remote. Let’s jump to the end first, and take a look at the final prompt I ended up with. I’m really happy with how it came out, I’ve deliberately broken it up into sections that can be enabled and disabled independently so other people can copy my implementation and turn on just the parts they like. In this prompt, is the closest bookmark. It’s colored red because it is both ahead and behind the remote it is tracking. (If only behind, magenta, if only ahead, cyan, and if caught up, green.) The small arrow to the right and the number 1 are telling us that our current is one non-empty commit beyond the local bookmark.

0 views
André Arko 5 months ago

Variable outputs from jj to a fixed length zsh array

While working on my shell prompt for jj , which will get a much longer post on its own shortly, I ran into a fascinating mismatch between different programs’ ideas of “empty”. To set the context, I’m trying to print out the change ID and the commit ID, which have two parts each. The “prefix”, which is the shortest unambiguous value that will match, and the “rest”, which is the other letters needed to reach the minimum length. (In this case 4 characters). In jj, the function prints each argument, delimited by a string. So that’s a list of four possible values, and the output looks something like . But what if the shortest unambiguous name is four characters? Then you get , or . Unfortunately, skips empty arguments entirely, so there’s nothing to indicate which value is missing. My genius plan (which consisted of assigning the output string to a zsh array) is now completely ruined—any time there are only 3 values, they are assigned to the wrong array indexes. After a few frustrating minutes, I had the evil genius idea of giving jj a sentinel value to print instead if the ID value was an empty string. Shockingly, that worked! It turns out jj thinks a null byte is not empty, and so it prints , including separators around the null. Unfortunately, that doesn’t work for zsh, which ignores nulls and treats multiple spaces as a single space for the purposes of splitting strings, so the array was still getting the wrong values.

0 views
André Arko 5 months ago

zsh cheat sheet

Today I was trying to figure out how to parse a string into an array in zsh, and eventually found the zsh cheat sheet . This should definitely be posted somewhere much easier to find, or maybe should just be the beginning of the zsh man page.

0 views
André Arko 5 months ago

gem clone

Today I stumbled across a neat tool (written by my Bundler and RubyGems co-maintainer @hsbt ) that makes it super easy to get a copy of a gem’s source repo: . . That’s it! Pretty good.

0 views
André Arko 5 months ago

fx.wtf

I’m a longtime user of , but its language is… not intuitive to me. I spent a lot of time searching for prewritten jq programs I can use, or reading the docs trying to figure out the exact thing that I want to do. Mostly, I think to myself “why do I have to learn a new language to manipulate JSON” every time I use it, even if my use is successful and does the thing I want. Good news, you don’t have to learn a new language to filter JSON anymore, thanks to . With a docs site at the excellent domain fx.wtf , and the ability to filter interactively or by providing Javascript as arguments, fx is the program I have always wished that jq was.

0 views