From 5e5786a1a379ee4bca7bc1aebb4ddb259dd927d5 Mon Sep 17 00:00:00 2001 From: Thomas Letan Date: Sun, 16 Jan 2022 14:19:01 +0100 Subject: Stacked Git is cool --- .gitignore | 1 + site/opinions/MonadTransformers.org | 3 +- site/opinions/StackedGit.org | 344 ++++++++++++++++++++++++++++++++++++ site/opinions/index.org | 6 + 4 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 site/opinions/StackedGit.org diff --git a/.gitignore b/.gitignore index bcc4632..6cec8ff 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ site/meta.html site/miscellaneous.html site/news/ColorlessThemes-0.2.html site/opinions/MonadTransformers.html +site/opinions/StackedGit.html site/opinions/index.html site/posts/.AlgebraicDatatypes.aux site/posts/.ClightIntroduction.aux diff --git a/site/opinions/MonadTransformers.org b/site/opinions/MonadTransformers.org index 6eea03e..08f8e1b 100644 --- a/site/opinions/MonadTransformers.org +++ b/site/opinions/MonadTransformers.org @@ -1,6 +1,7 @@ #+TITLE: Monad Transformers are a Great Abstraction -#+SERIES: ../opinions/index.html +#+SERIES: index.html +#+SERIES_NEXT: StackedGit.html [[mn:1][Time has passed since the publication of this article. Whether or not I remain in sync with its conclusions is an open diff --git a/site/opinions/StackedGit.org b/site/opinions/StackedGit.org new file mode 100644 index 0000000..95ad6f7 --- /dev/null +++ b/site/opinions/StackedGit.org @@ -0,0 +1,344 @@ +#+TITLE: How I Use Stacked Git at ~$WORK~ + +#+SERIES: index.html +#+SERIES_PREV: MonadTransformers.html + +According to [[https://lobste.rs/s/s6quvg/stacked_git][my Lobste.rs history]], I have run into [[https://stacked-git.github.io][Stacked Git]] in +early April, 2021, and I remember its promises hit a soft spot. +A few weeks later, I was submitting [[https://github.com/stacked-git/stgit/pull/100][a /pull request/ to teach Stacked +Git to sign commits]]. +It was all I needed to start using it at ~$WORK~, and now it has +become a cornerstone of my development workflow. + +#+BEGIN_EXPORT html +
site/opinions/StackedGit.org
+#+END_EXPORT + +* What is Stacked Git? + + Before going any further, it is probably a good idea to take a + moment and present Stacked Git. + The website introduces the tool as follows: + + #+begin_quote + Stacked Git, *StGit* for short, is an application for managing Git + commits as a stack of patches. + #+end_quote + + There is a few things to unpack here. + First and as its name suggests, Stacked Git is a tool built on top + of Git. + [[mn:1][My main takeaway from my Pijul adventure is connected to this. + Git is not limited to the ~git~ binary. + Git comes with a collection of powerful forges, nice editor plugins, + and years of good practices. + To this day, it’s neither the bugs nor the breaking changes that + made me quite Pijul. + Those were expected. + What I naively did not anticipate is the dry feeling that Pijul was + just the ~pijul~ binary, which left me with a lot of tasks to do + manually.]] + It is *not* a brand new VCS, and as a consequence you keep to use + all your existing tools and plugins[fn::I am looking at you, + Magit.]. + Secondly, Stacked Git helps you curate your Git history, by turning + your commits into patches, and your branches into stacks of patches. + This speaks to me, maybe because I have been fascinated by + email-based workflows for quite some time. + + To me, the two core features of Stacked Git are (1) allowing you to + name your commits, and (2) to navigate among them. + Together, they create a wonderful companion to help you keep your + history clean. + +* My Subset of Stacked Git + + I do not want this article to be a Stacked Git tutorial. + Fortunately, I don’t really use the tool at its full potential. + I only care about a relatively small subset of commands I feel + comfortable with and use daily. + + First, to decide which commits are part of my “stack of patches,” I + can count of these commands: + + - ~stg new NAME~ creates an empty commit, and gives it the name + ~NAME~. + Having a way to identify a patch with a meaningful name that is + resistant to rebase and amend is very nice. + These are two properties commit hashes do not have. + - ~stg uncommit NAME~ names the most recent commit under my + stack with ~NAME~ and integrates it into it. I do this when I am + tasked to work on a merge request made by a colleague, for + instance. + - ~stg commit~ removes from my stack its last patch. I do this when + said commit has been merged into ~master~. + + Once my stack of patches is ready, the fun begins. + + At a given time, a patch can either be (1) applied, (2) unapplied, + or (3) hidden. + On the one hand, if a patch is applied it is part of the Git history. + On the other hand, unapplying a patch means removing it from the + working branch (but not from the stack of patches of Stacked Git). + If a patch becomes unrelevant, but you don’t want to remove it + entierely because it can become handy later, you can hide it. + A hidden patch sits beside the stack of patches, and can be + reintegrated if need be. + + Analoguous to ~git log~ ---which allows you to visualize your Git + history---, ~stg series~ gives you a view the state of your stack of + patches. + Patches prefixed with ~+~ (or ~>~) are applied, while ~-~ means the + patch is unapplied. + + Then, + + - ~stg pop~ unapplies the patch on top of the list of applied + patches. + - ~stg push~ applies the patch on the bottom of the list of unapplied + patches. + - ~stg goto NAME~ unapplies or applies the necessary patches so that + ~NAME~ becomes the top patch of the list of applied patches. + + ~HEAD~ and the worktree are updated accordingly. + + In addition, ~stg sink~ and ~stg float~ allow to reorganize your + stack of patches, moving patches around. + Basically, they are like ~git rebase -i~, but without having to use + ~$EDITOR~. + + Modifying patches is done with ~stg refresh~. + It’s akin to ~git commit --amend~, except it is more powerful because + you can modify any applied patches with the ~-p~ option. + I’d always encourage you to ~stg goto~ first, because ~stg refresh + -p~ remains unfortunately error prone (nothing prevents you to target + the wrong patch). + But when used carefully, it can be very handy. + + [[mn:3][Stacked Git is supposedly able to detect, during a rebase, + which of your patches have been applied to your target branch. + I’d rather use ~stg uncommit~ before do the rebase, though.]] + Finally, ~stg rebase REF~ moves your stack of patches on top of + ~REF~. + It is akin to ~git rebase --onto~, but more straightforward. + What happens is Stacked Git pop all the patches of my stack, reset + the ~HEAD~ of the current branch to ~REF~, and tries applying the + patches one by one + In case of conflicts, the process stop, and I am left with an empty + patch, and a dirty worktree with conflicts to solve. + The hidden gem is that, contrary to ~git rebase~, the repository is + not “in the middle of a rebase.” + Suppos there are many conflicting patches still waiting in my stack + of patches, and an urgent task I need to take care of first. + I can just leave them here. + I can switch to another branch, and when I come back, I get my + patches back. + I call this feature “incremental rebases.” + + And that is basically it. + In a nutshell, Stacked Git equips commits with the same features as + branches. + +* My Stacked Git Workflow + + As mentioned in the introduction of this article, Stacked Git has + become a cornerstone of my workflow. + I’ve been asked a few times what this workflow is, and why Magit is + not enough[fn::It’s always about Magit ;).]. + So let’s try to do that. + But first, a warning. + Yes, because Stacked Git is only a wrapper above Git, everything I + will explain can be achieved using Git alone, especially if you are + a Magit wizard. + + Stacked Git makes just everything so more convenient to me. + +** Planning My Commits Ahead Of Time + + I’ve been introduced to Git with a pretty simple workflow: I am + supposed to start working on a feature, and once it’s ready, I + can commit, and move on to the next task on my todo list. + + To me, this approach is backward. + It makes you set your intent after the fact. + With Stacked Git, I often try to plan my final history /before + writing the very first line of code/. + Using ~stack new~, I create my patches, and take the time to write + their description. + It helps me visualizing where I want to go. + Then, I use ~stack goto~ to go back to the beginning of my stack, + and start working. + + It is not, and cannot be, an exact science. I often have to refine + them as my work progresses. + Yet, I think my Git history is cleaner, more focused, since I have + started this exercise. + +** Getting My Fixup Commits Right + + Reviews are a fundamental aspect of a software developer job. + At ~$WORK~, we use Gitlab and their merge requests workflow, + which I find very annoying, because it does not provide meaningful + ways to compare two versions of your submission[fn::There is a + notion of “versions” in Gitlab, but its ergonomics fall short of my + expectations for such tool.]. + + What we end up doing is creating “fixup commits”, and we push them + to Gitlab so that reviewers can easily verify that their feedback + have correctly been taken into account. + + A fixup commit is a commit that will eventually be squashed into + another. + You can understand it as a delayed ~git commit --amend~. + Git has some built-in features to manipulate them. + You create them with ~git commit --fixup=~, and they are + interpreted in a specific manner by ~git rebase -i~. + But they have always felt to me like a sordid hack. + It is way too easy to create a fixup commit that targets the wrong + commit, and you can end up with strange conflicts when you finally + squash them. + That being said, if used carefully, they are a powerful tool to + keep a Git history clean. + + I am not sure we are using them carefully, though. + + Some reviews can be excruciating, with dozens of comments to + address, and theoretically as many fixup commits to create. + Then you push all of them on Gitlab, and days later, after the + green light from the reviewer, you get to call ~git rebase~ + and discover your history is broken, you have tones of conflicts + to fix, and you’re good for a long afternoon of untangling. + + The main reason behind this mess is that you end up fixing a commit + from the ~HEAD~ of your working branch, not the commit itself. + But with Stacked Git, things are different. + With ~stg goto~, I put my working tree in the best state possible + to fix a commit: the commit itself. + I can use ~stg new~ to create a fixup commit, with a meaningful + name. + Then, I am forced to deal with the potential conflicts it brings + when I call ~stg push~. + + Once my reviewer is happy with my work, I can call ~stg squash~. + It is less automated than ~git rebase -i~, but the comfort I gained + during the development is worth this little annoyance. + +** Managing Stacked Merge Requests + + At ~$WORK~, we are trying to change how we deliver new features to + our ~master~ branch. + More precisely, we want to merge smaller contributions more + frequently. + We have had our fair share of large and complex merge requests that + were a nightmare to review in the past, and it’s really not a fun + position to be put in. + + For a few months, I have been involved in a project wherein we + decided /not/ to fall in the same trap again. + We agreed on a “planning of merge requests” and started working. + The first merge request was soon opened. + We’ve nominated a “owner” to take care of the review, and the rest + of the team carried on. + Before the first merge request was merged, the second one was + declared ready, and another owner was appointed. + Then, the owner of the first merge request had a baby, and yours + truly ended up having to manage two interdependent merge requests. + + It turns out Stacked Git is a wonderful tool to help me keep this + under control. + + I only have one branch, and I use the same workflow to deal with + feedbacks, even if they are coming from more than one one merge + request. + To remember the structure of everything, I just prefix the name of + my patches with a merge request nickname. + So my stack will look something like this: + + #+begin_src + + mr1-base + + mr1-tests + + mr1-doc + > mr2-command + - mr2-tests + #+end_src + + A reviewer leaves a hard-truth comment that requires a significant + rework of the oldest merge request? + ~stg goto~ reverts my worktree in the appropriate state, and ~stg + push~ allows me to deal with conflicts one patch at a time. + If at some point I need to spend more time on the oldest merge + request, I can continue my work, knowing the patches related to the + newest one are awaiting in my stack. + + The most annoying part is when the time comes to push everything. + I need to ~stg goto~ at the last patch of each merge request, and + ~git push HEAD:the-branch~. + It’s not horrible. + But I will probably try to automate it at some point. + +* Grievances + + Stacked Git have changed how I contribute to ~$SOFTWARE~ at ~$WORK~. + It makes my life so much easier, especially now that I am dealing + with stacked merge requests. + That being said, I still have some grievances I’d like to address at + some point, hopefully by contributing upstream. + + +** Stacked Git Feels Slow + + I suspect this is due to the conjunction of (1) ~$WORK~ repository + is large, and (2) Stacked Git is implemented in Python. + Maybe I am unfair, and the real causes lie somewhere else. + But the measurable fact I am witnessing is that ~stg series~ and + ~stg top~ (which prints the top patch name of the applied patches) + take 0.1s each. + + It’s not an issue when you call them from the shell, but it is when + you use them in your prompt. + Which I do. + This brings an annoying latency to my every interaction with the + repository. + +** I’d Like ~stg abort~ Please + + In this article, I have praised how Stacked Git allows for + its so-called ---by me--- incremental rebases. + However, the other side of the coin is that Stacked Git does not + have something analoguous to the ~--abort~ command-line argument + that you can pass to ~git cherry-pick~ and ~git rebase~. + Not really. + + [[mn:2][I don’t want to be unfair to Stacked Git here. Maybe the + documentation of Stacked Git provides useful tips to deal with this + issue, and I have just overlooked it.]] + Stacked Git has a command called ~stg undo~, which can achieve this + to some extent. + But ~stg undo~ does not like conflicts. + When called after a conflicting ~stg push~, its output is not + really helpful. + + #+begin_src + Error: Need to resolve conflicts first + stg undo: Command aborted (all changes rolled back) + #+end_src + + The only way out that I am aware of is: + + - ~git add~ the files with conflicts. + - ~stg refresh~ to fix recover. + - ~stg undo~, twice. + +I’d argue we have seen better UXs. + +* Conclusion + + Overall, I am really thankful to Stacked Git’s authors! + Thank you! + You are making my interactions with Git fun and carefree. + You provide me some of the convenience of patch-based VCS like [[http://darcs.net][Darcs]] + and [[https://pijul.org][Pijul]], but without sacrificing the power of Git. + + I encourage anyone to at least give it a try, and I really hope I + will be able to contribute back to Stacked Git when ~$WORK~ is a bit + less crazy. diff --git a/site/opinions/index.org b/site/opinions/index.org index 58b0407..309aaed 100644 --- a/site/opinions/index.org +++ b/site/opinions/index.org @@ -7,6 +7,12 @@ separate indexes. I would encourage you to use your critical mind while reading these write-ups. +** 2022 + + - [[./StackedGit.org][How I Use Stacked Git at ~$WORK~]] :: + I’ve been using Stacked Git at ~work~ for a few months, and as of + January 2022, + ** 2017 - [[./MonadTransformers.org][Monad Transformers are a Great Abstraction]] :: -- cgit v1.2.3