summaryrefslogtreecommitdiffstats
path: root/site/opinions/StackedGit.org
blob: 95ad6f7e81fee60656650fbfe42b545b5c19e4fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
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
<div id="history">site/opinions/StackedGit.org</div>
#+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=<HASH>~, 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.