Posts in Git (15 found)
Grumpy Gamer 1 weeks ago

Git, JSON and Markdown walk into bar

I want to talk about three things that has fundamentally changed my dev-life. There are a lot of things, like ImGUI, that are very amazing and useful but they don’t provide a general solution across many problems. In no particular order… I’ve been using Git since 2010 and it really has changed my dev-life. I’d used version control before that, mainly Perform, SVN and PVSC, but Git felt nice and unobtrusive and I like that everything was local until I pushed to the server. It’s both nice and annoying that you can’t lock files (like art). You can (kind of) with LFS but that feels tacked on and not ready for primetime. Don’t think so? Try explaining installing and using it to a non-technical artist sometime. Git can be frustrating if you’re trying to do anything but the basics. Accidentally check in a secret file months ago and need to scrub it? Good luck with that. There are ways but it requires a lot of Git-Fu. I mainly use a GUI for git ( Fork ) and that takes most of the pain away. I do use the command line, but mostly in automation scripts. Before Markdown became the de-facto standard, I used my own custom format. It worked but wasn’t great and only I understood it. Markdown has it’s issues when you start using the more esoteric features. I’m also annoyed at bold and italics notation. Why is italics and bold is ? Why not and . That would make a lot more sense to me. I also have issue with it’s creator, John Gruber. He is a highly annoying smug Apple Fanboy. His writing was fine in the early days when Apple was #3, but got intolerable as Apple became the 800lb gorilla. It’s changed recently as Apple has snubbed him but I still can’t read anything he writes. But, I like his Markdown. I use JSON for just about every data file format in my games. JSON was created by Douglas Crockford as a notation for Javascript objects. I worked with Doug Crockford at Lucasfilm for several years. I always had a lot of respect for Doug and was somewhat intimidated (in a good way) by him. Doug was also the producer for the Nintendo Maniac Mansion. As much as I love JSON, there are some things about it that annoy me. I dislike that trailing commas are not allowed There is no need for this and it makes writing out valid JSON more complex. I also don’t like you that have to wrap keys names in quotes if they are simple ascii. I wrote a custom JSON parser I use in all my games that relaxes these, but then general JSON readers fail on my data.

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

Thoughts on running my own forgejo (hosted git with web stuff) server

I’ve been experimenting with git for a few months now , and I’ve been finding it both useful and enjoyable. It’s nice to learn something new, with a purpose in mind. For keeping dot files available across machines, in circumstances where Nextcloud is not a great option, I’ve been using git on a locked-down, private server. I commit locally, on whatever machine I make the change, and then push to the remote git server. This is just plain git, with no web front end or anything. Since it is just for me, and since I don’t want to expose it to the Internet, this has been fine, and it works well. I also wanted a git server with a web interface, which I can use for public-facing stuff. For that, I went with forgejo. I am always hesitant to say that installing and running something is easy, but that is subjective, but, for me, so far, it has been a very pleasant experience. At the moment, I am only using it for hosting some documentation - some sample terms for fedi instances - rather than code. I like it, and it is easy for me to use, pushing to it from a terminal. I am running it on a tiny container on a proxmox server; I might need to beef it up a little, but so far, it is doing pretty well, even in the face of sustained distributed traffic when I post links to it to the fediverse. I was pleased to see so many options for importing from other forges, including GitHub. As it turns out, I have not bothered to move anything from GitHub, as I’ve nothing there that I actually value or which I think would be of use to others. I’ll probably just delete those repos. But I very much like it that the option is there, and simple. Being my own instance, I am the only user of it, and, sadly, forgejo’s federated functionality - which would enable a user on another foregjo instance to make pull requests, raise issues etc. - is not there yet. So, for now, it is a bit isolated: it is there, on the web, but no-one else can raise issues and the like, and that feels like a shame for a collaboration platform. Perhaps I just didn’t think this through well enough when I picked a self-hosted instance of forgejo; I might have been better going with GitLab, or the like. I am always nervous about putting private information on a server accessible from the Internet. I have a Nextcloud instance, but it is not accessible from the Internet, even though I could see advantages to that. I lock down my mail server, so that one cannot log into IMAP over the Internet. I don’t expose a webmail interface. So, for now, I have not experimented with private repositories, and I am sticking with the separate git server (mentioned above) for things which I want to keep to myself. Perhaps I worry too much, and perhaps I should have some more faith in both my own sysadmin skills, and also the security of the services themselves. But, for now, separation seems like a better plan.

0 views
neilzone 2 months ago

Fixing a fatal git error when upgrading LiberaForms to v4.2.0 by changing my git config origin from Gitlab to Codeberg

If you are the kind of person who has bothered to click the link to read this, this may already be obvious. But it stumped me for a bit, so I’m putting it here in case it helps others. I run an instance of LiberaForms . I like it very much. I was keen to upgrade to v4.2.0 in particular, because of its support for conditional form fields . When I followed the upgrade process in the blog post, I ran: and got the error The problem, it turns out, is that LiberaForms used to use Gitlab for source distribution / development, but has recently moved to Codeberg. I had installed LiberaForms from Gitland, so my was: v4.2.0 is not on Gitlab, but on their new choice of forge, Codeberg. I changed to: And re-ran . That worked, as did the rest of the upgrade instructions. There might be a better / proper way of changing an origin than hand-editing the config file, but this worked for me. Update : thanks to the kind person who suggested .

0 views

Posting through it

I'm posting this from a very, very rough cut at a bespoke blogging client I've been having my friend Claude build out over the past couple days. I've long suspected that "just edit text files on disk to make blog posts" is, to a certain kind of person, a great sounding idea...but not actually the way to get me to blog. The problem is that my blog is...a bunch of text files in a git repository that's compiled into a website by a tool called "Eleventy" that runs whenever I put a file in a certain directory of this git repository and push that up to GitHub. There's no API because there's no server. And I've never learned Swift/Cocoa/etc, so building macOS and iOS tooling to create a graphical blogging client has felt...not all that plausible. Over the past year or two, things have been changing pretty fast. We have AI agents that have been trained on...well, pretty much everything humans have ever written. And they're pretty good at stringing together software. So, on a whim, I asked Claude to whip me up a blogging client that talks to GitHub in just the right way. This is the very first post using that new tool, which I'm calling "Post Through It." Ok, technically, this is the fourth post. But it's the first one I've actually been able to add any content to.

0 views
tekin.co.uk 1 years ago

Different ways to use “–patch” in Git

I’ve written previously about using to interactively stage changes . But did you know that you can use (aka ) to similar effect with other Git commands? Let’s take a look… is great for temporarily stashing changes that you want to apply later, and handily it also supports selectively stashing changes with the flag: Bonus tip: you can also selectively stash entire files using to disambiguate the command from the paths you want stashing: You can use the command to discard local changes and restore files to their last committed state. It can also be called with the flag to interactively select specific hunks to discard: Note the different phrasing of the prompt on the last line: Here we are choosing the changes we want to discard . Be careful, this is a destructive change, and because these are unstaged and uncommitted changes git won’t be able to help you recover the changes once they’ve been discarded!

0 views
Steve Klabnik 1 years ago

Against Names

There’s an old saying: There are only two hard things in Computer Science: cache invalidation and naming things. ― Phil Karlton I also appreciate the joke version that adds “and off by one errors.” Lately, I’ve been thinking about this saying, combined with another old joke: “The patient says, “Doctor, it hurts when I do this.” The doctor says, “Then don’t do that!” ― Henny Youngman Specifically, if naming things is so hard… why do we insist on doing it all the time? Now, I am not actually claiming we should stop giving things names. But I have had at least two situations recently where I previously found names to be kinda critical, and then I changed to systems which didn’t use names, and I think it improved the situation. One of the most famous examples of not giving something a name, lambdas/closures, took some time to catch on. But many folks already recognize that naming every single function isn’t always neccesary. I wonder if there are more circumstances where I’ve been naming things where I didn’t actually have to. Anyway, here’s my two recent examples: I haven’t written much about it on my blog yet, but I’m fully converted away from git to jj . I’ll say more about this in the future, but one major difference between the two is that jj has anonymous branches. If, like me, you are a huge fan of git, this sounds like a contradiction. After all, the whole thing about branches are that they’re a name for some point in the DAG. How do you have a nameless name? Here’s some output from : Here’s some sample output from jj log: Here, we are working on change . ( means the working copy.) There are colors in the real CLI to make the differences more obvious, and to show you unique prefixes, so for example, you probably only need or instead of to uniquely identify the change. I’ll use the full IDs since there’s no syntax highlighting here. We have two anonymous branches here. They have the change IDs of and . The log output shows the summary line of their messages, so we can see “create hello and goodbye functions” on one branch, and “add better documentation” on the other. You don’t need an additional branch name: the change ID is already there. If you want to add even more better documentation, (or again, likely in practice) and you’re off to the races. (jj new makes a new change off of the parent you specify.) (And if you’re in a larger repo with more outstanding branches, you can ask to show specific subsets of commits. It has a powerful DSL that lets you do so. For example, say you only want to see your commits, can do that for you.) That’s all there is to it. We already have the commit messages and IDs, giving an additional identifier doesn’t help that much. In practice, I haven’t missed named branches at all. And in fact, I kind of really appreciate not bothering to come up with a name, and then eventually remembering to delete that name once the PR lands, stuff like that. Life is easier. Another technology I have learned recently is tailwind . But Tailwind is just one way of doing a technique that has a few names, I’m going to go with “utility CSS”. The idea is in opposition to “semantic CSS.” To crib an example from a blog post by the author of Tailwind (which does a better job of thoroughly explaining why doing utility CSS is a good thing, you should go read it), semantic CSS is when you do this: Whereas, with Tailwind, you end up instead having something like this: We don’t have a new semantic name , but instead describe what we want to be done to our element via a utility class. So the thing is, as a previous semantic CSS enjoyer, this feels like using inline styling. But there’s a few significant differences. The first one is, you’re not writing plain CSS, you are re-using building blocks that are defined for you. The abstraction is in building those utility classes. This means you’re not writing new CSS when you need to add functionality, which to me is a great sign that the abstraction is working. It’s also that there is some sleight of hand going on here, as we do, on another level. An objection that gets raised to doing things this way is “what happens when you need to update a bunch of similar styles?” And the answer for that is components. That is, it’s not so much that utility CSS says that semantic names are bad, it’s that semantic names at the tag level are the wrong level of abstraction to use names. To sort of mix metaphors, consider the lambda/closure example. Here’s a random function in Rust: The is unfortunate, but this function takes a list of numbers, selects for the even ones, and then sums them. Here, we have a closure, the argument to , but it’s inside a named function, . This is what using Tailwind feels like to me, we use names for higher level concepts (components), and then keep things semantically anonymous for some of the tasks inside of them (markup). Heck, even the most pro-semantic-styles folks don’t advocate that you must give every single element a class. Everyone recognizes the value of anonymous things sometimes, it’s just a matter of what level of abstraction deserves to get named. Here’s my post about this post on BlueSky: Against Names: steveklabnik.com/writing/agai... Against Names

0 views
Bill Mill 1 years ago

How to debug git with visual studio code on a mac

I needed to figure out how to debug the binary to figure out exactly what it was doing, and I thought I'd share the setup because it took me a little while to get going. make a directory: open the git directory with visual studio code: create a file in the directory called that tells vs code how to start for debugging. Its contents should be: Open the file in the editor Set a breakpoint on the first line inside the function; in my current version of git, that's located at line 1504 Switch to the tab of VS code click on , the green arrow located at the top left of the window, or press F5 to do the same thing. If all goes well, you should now be controlling the execution of git inside VS code! Go ahead and dig in to try and understand what's going on. I might write more about what it is that's going on in there, but I'm not sure - let me know if you would like to understand some part of it better.

0 views

Jujutsu Strategies

Today I want to talk about jujutsu , aka , which describes itself as being “a Git-compatible VCS that is both simple and powerful”. This is selling itself short. Picking up has been the best change I’ve made to my developer workflow in over a decade. Before , I was your ordinary git user. I did things on Github and knew a handful of git commands. Sometimes I did cherry picks. Very occasionally I’d do a non-trivial rebase, but I had learned to stay away from that unless necessary, because rebasing things was a perfect way of fucking up the git repo. And then, God forbid, I’d have to re-learn about the reflog and try to unhose myself. You know. Just everyday git stuff. What I hadn’t realized until picking up was just how awful the whole git experience is. Like, everything about it sucks. With git, you need to pick a branch name for your feature before you’ve made the feature. What if while doing the work you come up with a better understanding of the problem? With git, you can stack PRs, but if you do, you’d better hope the reviewers don’t want any non-trivial changes in the first PR, or else you’ll be playing commit tag, trying to make sure all of your branches agree on the state of the world. With git, you can do an interactive rebase and move things relative to a merge commit, but you’d better make sure you know how works, or else you’re going to spend the next several hours resolving the same conflicts across every single commit from the merge. We all know our commit history should tell the story of how our code has evolved. But with git, we all feel a little bit ashamed that our commit histories don’t , because doing so requires a huge amount of extra work after the fact, and means you’ll probably run into all of the problems listed above. Somehow, that’s just the state of the world that we all take for granted. Version control Stockholm syndrome. Git sucks. And jujutsu is the answer. The first half of this post is an amuse bouche to pique your interest, and hopefully convince you to give a go. You won’t regret it. The second half is on effective strategies I’ve found for using in my day to day job. In git, the default unit of work is a “commit.” In , it’s a “change.” In practice, the two are interchangeable. The difference is all in the perspective. A commit is a unit of work that you’ve committed to the git log. And having done that, you’re committed to it. If that unit of work turns out to not have been the entire story (and it rarely is), you must make another commit on top that fixes the problem. The only choice you have is whether or not you want to squash rebase it on top of the original change. A change, on the other hand, is just a unit of work. If you want, you can pretend it’s a commit. But the difference is that you can always go back and edit it. At any time. When you’re done, automatically rebases all subsequent changes on top of it. It’s amazing, and makes you feel like a time traveler. Let’s take a real example from my day job. At work, I’m currently finessing a giant refactor, which involves reverse engineering what the code currently does, making a generic interface for that operation, pulling apart the inline code into instances of that interface, and then rewriting the original callsite against the interface. After an honest day’s work, my looked something like this: This is the version of the . On the left, we see a (linear) ascii tree of changes, with the most recent being at the top. The current change, marked with has id and description . I’m now ready to add a new change, which I can do via : I then went on my merry way, rewriting the second callsite. And then, suddenly, out of nowhere, DISASTER. While working on the second callsite, I realized my original abstraction didn’t actually help at callsite 2. I had gotten the interface wrong. In git land, situations like these are hard. Do you just add a new commit, changing the interface, and hope your coworkers don’t notice lest they look down on you? Or do you do a rebase? Or do you just abandon the branch entirely, and hope that you can cherry pick the intermediary commits. In , you just go fix the change via : and then you update your interface before jumping back ( ) to get the job done. Honestly, time traveler stuff. Of course, sometimes doing this results in a conflict, but is happy to just keep the conflict markers around for you. It’s much, much less traumatic than in git. Branches play a much diminished role in . Changes don’t need to be associated to any branch, which means you’re usually working in what git calls a detached head state. This probably makes you nervous if you’ve still got the git Stockholm syndrome, but it’s not a big deal in . In , the only reason you need branches is to ship code off to your git-loving colleagues. Because changes don’t need to be associated to a branch, this allows for workflows that git might consider “unnatural,” or at least unwieldy. For example, I’ll often just do a bunch of work (rewriting history as I go), and figure out how to split it into PRs after the fact. Once I’m ~ten changes away from an obvious stopping point, I’ll go back, mark one of the change as the head of a branch , and then continue on my way. This marks change as the head of a branch , but this action doesn’t otherwise have any significance to ; my change tree hasn’t changed in the slightest. It now looks like this: where the only difference is the line . Now when sends this off to git, the branch will have one commit for each change in . If my colleagues ask for changes during code review, I just add the change somewhere in my change tree, and it automatically propagates downstream to the changes that will be in my next PR. No more cherry picking. No more inter-branch merge commits. I use the same workflow I would in that I would if there weren’t a PR in progress. It just works. It’s amazing. The use and abuse of the dev branch pattern , makes a great argument for a particular git workflow in which you have all of your branches based on a local branch. Inside of this branch, you make any changes relevant to your local developer experience, where you change default configuration options, or add extra logging, or whatever. The idea is that you want to keep all of your private changes somewhere organized, but not have to worry about those changes accidentally ending up in your PRs. I’ve never actually used this in a git workflow, but it makes even more sense in a repository. At time of writing, my change tree at work looks something like the following: Here you can see I’ve got quite a few things on the go! , and are all branched directly off of , which correspond to PRs I currently have waiting for review. and are stacked changes, waiting on to land. The ascii tree here is worth its weight in gold in keeping track of where all my changes are. You’ll notice that my branch is labeled as , which is to say it’s a change with no diff. But even so, I’ve found it immensely helpful to keep around. Because when my coworkers’ changes land in , I need only rebase on top of the new changes to , and will do the rest. Let’s say now has conflicts. I can just go and edit to fix the conflicts, and that fix will be propagated to and !!!! YOU JUST FIX THE CONFLICT ONCE, FOR ALL OF YOUR PULL REQUESTS. IT’S ACTUALLY AMAZING. In , sets of changes are first class objects, known (somewhat surprisingly) as revsets. Revsets are created algebraically by way of a little, purely functional language that manipulates sets. The id of any change is a singleton revset. We can take the union of two revsets with , and the intersection with . We can take the complement of a revset via . We can get descendants of a revset via , and its ancestors in the obvious way. Revsets took me a little work to wrap my head around, but it’s been well worth the investment. Yesterday I somehow borked my change (????), so I just made , and then reparented the immediate children of over to in one go. You can get the children of a revset via , so this was done via . Stuff like that is kinda neat, but the best use of revsets in my opinion is to customize the experience in exactly the right way for you. For example, I do a lot of stacked PRs, and I want my to reflect that. So my default revset for only shows me the changes that are in my “current PR”. It’s a bit hard to explain, but it works like an accordion. I mark my PRs with branches, and my revset will only show me the changes from the most immediate ancestral branch to the most immediate descendant branch. That is, my log acts as an accordion, and collapses any changes that are not part of the PR I’m currently looking at. But, it’s helpful to keep track of where I am in the bigger change tree, so my default revset will also show me how my PR is related to all of my other PRs. The tree we looked at earlier is in fact the closed version of this accordion. When you change to be inside of one of the PRs, it immediately expands to give you all of the local context, without sacrificing how it fits into the larger whole: The coolest part about the revset UI is that you can make your own named revsets, by adding them as aliases to . Here’s the definition of my accordioning revset: You can see from that we always show (the current edit), all of the named bases (currently just , but you might want to add ), and all of the named branches. It then shows everything from to , which is to say, the changes in the branch leading up to , as well as everything from to the beginning of the next (stacked) branch. Finally, we show all the leafs of the change tree downstream of , which is nice when you haven’t yet done enough work to consider sending off a PR. Jujutsu is absolutely amazing, and is well worth the four hours of your life it will take you to pick up. If you’re looking for some more introductory material, look at jj init and Steve’s jujutsu tutorial

0 views
Gabriel Garrido 1 years ago

Hosting my own software forge

Update : I’ve since moved on to stagit for my public-facing repositories. The repositories are still found at git.garrido.io , and can be cloned using the dumb http protocol . It’s been a couple of months since I started hosting my own software forge . Since then I’ve used it as a remote for all of my git repositories, as a registry for container images, to store gists , and for continuous integration (CI). I chose Forgejo because, for my use case, it has feature parity with Github, while also being open-source, lightweight, and simple to operate. It also happens to be governed by its contributors, and is stewarded by Codeberg , non-profit organization. Unlike Github, I’m not using Forgejo to collaborate on other people’s projects. My server is a single-user instance. This isn’t much of a problem for me as my main motivation to host a software forge is not social. However, something particularly exciting about Forgejo is that federation is being implemented via ForgeFed , an ActivityPub extension for software forges: ForgeFed is a federation protocol for software forges and code collaboration tools for the software development lifecycle and ecosystem. This includes repository hosting websites, issue trackers, code review applications, and more. ForgeFed provides a common substrate for people to create interoperable code collaboration websites and applications. What’s great about version control systems like git is that it is easy to move code from one remote to another as one deems approriate. Yet, tangential artifacts that are equally as important are not as portable or accessible. Initiatives like ForgeFed pave the way to a world in which we’re not dependent on a centralized entity whose interests and incentives may no longer align with one’s own. Hosting Forgejo has been uneventful and virtually maintenance-free. The setup was quick, though configuring Forgejo Runners did require some trial and error as some parts of the documentation were not entirely clear to me. With regards to performance, Forgejo does run lightly. A quick glance at Grafana tells me that in the past seven days it has been using approximately 150MB of memory and 1-2% of CPU on a low-powered VPS. Such footprint would be even smaller if I were not using this for CI. Like all other services that I self host, Forgejo and Forgejo Runners run in Docker containers. This adds some overhead but that’s the tradeoff I choose to keep things simpler on the host 1 . I use Tailscale to access the server privately over SSH while also exposing the public repositories through a proxy on a separate public server. As for backups, Forgejo has a helpful command to export its files and SQLite database. I use this to run automated backups using Restic . When I decide to upgrade versions I read their well-written release notes , bumps the container’s image, and run an Ansible playbook that runs a backup, restarts the containers, and migrates the database if necessary. It’s worth pointing out that Forgejo just released their first Long Term Support release so the maintenance burden could be eased further by sticking with the LTS version and applying minor updates. Forgejo is written in Go so running it on “bare metal” is as simple as it gets, but I still use a container to keep logging, deployments, and configuration management consistent across many services that I host on a single VPS.  ↩︎ Forgejo is written in Go so running it on “bare metal” is as simple as it gets, but I still use a container to keep logging, deployments, and configuration management consistent across many services that I host on a single VPS.  ↩︎

0 views

Letting the LLM draft my commit messages

My friend harper built a neat tool to let the llm draft your commit messages . I ended up tweaking his code a bit for my own use. I wanted commit messages that were a bit more...boring. And I wasn't really happy with how the original code put together the 'why' of the commit message. I made changes to harper's prompt to try to rein things in just a bit and also hacked up his git hook to tweak the model's "temperature" and to provide full-file context to the llm to give it a bit more to go on when writing a commit message. I find that with this setup, I do need to edit my commit messages about 90% of the time, but that those edits are relatively minor and that I'm ending up with better, more detailed commit messages. (Also, I am generally much more productive when I'm hacking up something bad than when I'm staring at a blank screen.) Although, having said that, when I added the first version of this post to the git repo for my blog, it generated this commit message, which I accepted unchanged: You should refer to harper's post for context about what the heck this tool does and also how to set it up. My script: My prompt lives inside llm's "prompt templates" feature at

0 views
Can ELMA 3 years ago

Ignore files without modifying .gitignore

The file is also tracked so you've to expose a list of your ignored files when you added them to the file. This may not be what you want. In the git folder, there's a file called at . This file just works like the .gitignore file but without being tracked. You can ignore untracked files with this file without exposing them. The exclude file does the job if you only want to ignore untracked files. But what if those files are already tracked? In such cases, you can use the below commands to ignore a specific file or path. To create aliases for these commands:

0 views
tekin.co.uk 3 years ago

List your Git branches by recent activity

Even if you’re diligent and regularly delete merged and stale branches you may still find it hard to pick out a particular branch from the alphabetically-sorted output of . How about something more useful, like seeing them listed based on their freshness? The command accepts a option which we can use to list our branches based on the last committer date: We can also use the option to include the exact time and see just how fresh each branch is: Or for something more friendly and easy-to-parse we can ask for a relative date: That’s better! Let’s add this as a alias to our Git config:

0 views
tekin.co.uk 5 years ago

Better Git diff output for Ruby, Python, Elixir, Go and more

The regular Git users amongst you will be familiar with the diff output that breaks down into “hunks” like so: The first line (starting ) is known as the hunk header, and is there to help orientate the change. It gives us the line numbers for the change (the numbers between the ), but also a textual description for the enclosing context where the change happened, in this example . Git tries to figure out this enclosing context, whether it’s a function, module or class definition. For C-like languages it’s pretty good at this. But for the Ruby example above it’s failed to show us the immediate context, which is actually a method called . That’s because out of the box Git isn’t able to recognise the Ruby syntax for a method definition, which would be . What we really want to see is: And it’s not just Ruby where Git struggles to figure out the correct enclosing context. Many other programming languages and file formats also get short-changed when it comes to the hunk header context. Thankfully, it’s not only possible to configure a custom regex specific to your language to help Git better orient itself, there’s even a pre-defined set of patterns for many languages and formats right there in Git . All we have to do is tell Git which patterns to use for our file extensions.

0 views