<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kamon Ayeva</title>
    <description>The latest articles on DEV Community by Kamon Ayeva (@kamon-shellcraft).</description>
    <link>https://dev.to/kamon-shellcraft</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4075224%2F6e05eb91-7da1-4773-b3c3-48ca782ad156.jpg</url>
      <title>DEV Community: Kamon Ayeva</title>
      <link>https://dev.to/kamon-shellcraft</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kamon-shellcraft"/>
    <language>en</language>
    <item>
      <title>Disk full? broot's whale-spotting mode plus Ctrl-G turns your cleanup into one command</title>
      <dc:creator>Kamon Ayeva</dc:creator>
      <pubDate>Fri, 25 Sep 2026 18:59:48 +0000</pubDate>
      <link>https://dev.to/kamon-shellcraft/disk-full-broots-whale-spotting-mode-plus-ctrl-g-turns-your-cleanup-into-one-command-cab</link>
      <guid>https://dev.to/kamon-shellcraft/disk-full-broots-whale-spotting-mode-plus-ctrl-g-turns-your-cleanup-into-one-command-cab</guid>
      <description>&lt;p&gt;Every engineer knows the disk-full afternoon. The build or the backup job fails, and you end up in the same loop using &lt;code&gt;du -sh&lt;/code&gt; and &lt;code&gt;rm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;/ | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;that-one-cache-dir
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;/ | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;another-thing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works but, since the listing you get is flat, you see directory totals, not the files inside them. And you delete one directory at a time, with no way to collect a set of files, review the set, and delete it in one go.&lt;/p&gt;

&lt;p&gt;This article is about a workflow I use instead, built on broot's staging area:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;br -w&lt;/code&gt; lists files sorted by size, &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Ctrl-G&lt;/code&gt; collects the files you want to delete into a review panel,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:rm&lt;/code&gt; deletes the whole set.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's explore the details, while running the workflow in a real directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: get the sorted view with &lt;code&gt;br -w&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As a reminder, broot is launched using &lt;code&gt;br&lt;/code&gt;, the shell function; launching plain &lt;code&gt;broot&lt;/code&gt; works too, but &lt;code&gt;br&lt;/code&gt; is what makes &lt;code&gt;cd&lt;/code&gt; work (when you are navigating the tree and want to land in the contextual folder using the &lt;code&gt;:cd&lt;/code&gt; verb). &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-w&lt;/code&gt; flag is for the &lt;em&gt;whale-spotting&lt;/em&gt; mode: it sorts files and directories by size, biggest first. Using this feature, when a disk fills up, you can see the files that are using the space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;br &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, the tree shows each directory with its total, and you expand any of them by moving down with the arrow keys and hitting &lt;code&gt;Enter&lt;/code&gt; on a folder.&lt;/p&gt;

&lt;p&gt;The difference from using &lt;code&gt;du | sort -h&lt;/code&gt; is that you're inside one interactive tree, moving from the big directories down to the big files inside them, without running a new command per level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: build the set of files to delete with &lt;code&gt;Ctrl-G&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Next, move the cursor to a file you want to delete and press &lt;code&gt;Ctrl-G&lt;/code&gt;. A marker appears next to the filename, and a second panel opens on the right; it's the staging area, showing the files you have collected so far. Press &lt;code&gt;Ctrl-G&lt;/code&gt; on each new file you want to delete, and it is added to that area. And you can remove a file from the set, by pressing &lt;code&gt;Ctrl-G&lt;/code&gt; on it a second time.&lt;/p&gt;

&lt;p&gt;Keep navigating the tree and staging files as you go. The panel refreshes everytime there is a staging or unstaging action, showing the files you collected and their total size. You can see directly how much space can freed by deleting the set, instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: review the set, then delete it with &lt;code&gt;:rm&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;One detail that confused me at first: when the staging panel opens, it does not take the focus. The focus stays on the tree panel, so you can keep navigating. This means that a verb you type applies to the tree panel's selection, not the staged set, until you move the focus.&lt;/p&gt;

&lt;p&gt;The keystroke to move the focus is &lt;code&gt;Ctrl-Right&lt;/code&gt;. Once the staging panel is focused, you can review it like any broot panel: scroll it, filter it, and unstage files you no longer want. The whole deletion set is visible in one place before anything is gone.&lt;/p&gt;

&lt;p&gt;In case the &lt;code&gt;Ctrl-Right&lt;/code&gt; keystroke do not reach broot, as it happens on my macOS, just use the "typed verb" fallback: &lt;code&gt;:panel_right&lt;/code&gt;. And, for going back to the tree panel, there is its counterpart &lt;code&gt;:panel_left_no_open&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When the staged set is right, type the verb needed for deletion (&lt;code&gt;:rm&lt;/code&gt;) and read the status line before you go ahead by hitting Enter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:rm
Enter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One caveat from my run (using both broot 1.58.0 on macOS and broot 1.58.0 on Ubuntu 24.04): with several files staged and the staging panel focused, the status line showed only one of them as selected — but pressing &lt;code&gt;Enter&lt;/code&gt; removed the whole staged set. So the status bar report is partial, but the staging panel itself is the true picture of what will be deleted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsh33sa17k7xhao65tous.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsh33sa17k7xhao65tous.png" alt="Broot's br -w: stage files to delete, review the set, and delete in one go" width="799" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, using broot this way, the cleanup loop becomes: list (stage), review, delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you script the cleanup
&lt;/h2&gt;

&lt;p&gt;Of course you could also use &lt;code&gt;find&lt;/code&gt; with a size filter, or a &lt;code&gt;du&lt;/code&gt; piped to &lt;code&gt;xargs rm&lt;/code&gt;. And for a recurring need, scheduling that command (cron or equivalent) is the right solution.&lt;/p&gt;

&lt;p&gt;But the first few times, you want to do it interactively. You don't yet know what is big and why, so the decision about what to delete needs a human check: is that &lt;code&gt;node_modules&lt;/code&gt; directory obsolete? is that log file still being written? does that &lt;code&gt;.db&lt;/code&gt; file belong to something you forgot you installed? The staging feature shows you the directory file by file, and you understand what is safe to delete. When the pattern becomes obvious (things like old logs past 90 days), then write a script for exactly that. You will know what the script touches, because you staged that set by hand.&lt;/p&gt;

&lt;p&gt;In conclusion, do it interactively first, and script it once you know the rules for what to delete. The interactive session is how you identify the rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the keystrokes do nothing
&lt;/h2&gt;

&lt;p&gt;It might happen that &lt;code&gt;Ctrl-G&lt;/code&gt; or &lt;code&gt;Ctrl-Right&lt;/code&gt; does nothing in your terminal. Some terminals intercept those key combinations before broot receives them. But the workflow does not depend on the keys: each one has a typed verb that does the same thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:toggle_stage           # or :stage / :unstage — equivalent to Ctrl-G
:open_staging_area      # or :osa — force the staging panel open
:clear_stage            # or :cls — empty the staging area, start over
:panel_right            # focus the staging panel (if open)
:panel_left_no_open     # focus the tree panel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a keystroke does not work, type the verb instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other verbs to use on the staged set
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;rm&lt;/code&gt; is the disk-cleanup verb, the staging set works with anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:cp {dest}&lt;/code&gt;: copy the whole set somewhere first, then delete it; useful for a backup before the cleanup.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:mv {dest}&lt;/code&gt;: move the set to another volume instead of deleting it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:chmod +x&lt;/code&gt;: apply a mode to every staged file, when the task is about permissions instead of space.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is one verb that works differently: &lt;code&gt;:zip&lt;/code&gt; takes the whole staged set as the arguments of a single &lt;code&gt;zip&lt;/code&gt; command, so the resulting archive holds everything. While the verbs above run once per staged file, &lt;code&gt;:zip&lt;/code&gt; runs once for the whole set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Inside broot, after staging several files:
:zip /tmp/backup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the archive-first variant of the workflow: &lt;code&gt;br -w&lt;/code&gt;, stage the big old logs, run one &lt;code&gt;:zip&lt;/code&gt; (in the above example, this creates &lt;code&gt;/tmp/backup.zip&lt;/code&gt;), then decide about deletion once the archive exists.&lt;/p&gt;




&lt;p&gt;This article expands issue #9 of my newsletter, &lt;a href="https://www.shellcrafthq.com/" rel="noopener noreferrer"&gt;Shellcraft&lt;/a&gt; — one piece of terminal craft per week.&lt;/p&gt;

</description>
      <category>broot</category>
      <category>terminal</category>
      <category>sysadmin</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Lazygit's M Key Hides Four Merge Options — Here's the Git History Each One Leaves Behind</title>
      <dc:creator>Kamon Ayeva</dc:creator>
      <pubDate>Sat, 12 Sep 2026 12:37:43 +0000</pubDate>
      <link>https://dev.to/kamon-shellcraft/lazygits-m-key-hides-four-merge-options-heres-the-git-history-each-one-leaves-behind-4pg</link>
      <guid>https://dev.to/kamon-shellcraft/lazygits-m-key-hides-four-merge-options-heres-the-git-history-each-one-leaves-behind-4pg</guid>
      <description>&lt;p&gt;I use lazygit daily, and for a long time &lt;code&gt;M&lt;/code&gt; was just "the merge key". Press it, pick the option, the merge happens. It took me a while to actually slow down and pay attention to the options in the menu. Each option runs the same merge, but the commit graph left behind is different, and that influences the git history.&lt;/p&gt;

&lt;p&gt;So I did what I do with any keystroke I don't fully understand: I built a throwaway repo, pressed &lt;code&gt;M&lt;/code&gt; one option at a time, and read the history graph after each one. This article is the result — each option, the history it leaves, and at the end, the two &lt;code&gt;git log&lt;/code&gt; commands that show you the merge shape of any repo you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one key with four options
&lt;/h2&gt;

&lt;p&gt;In lazygit's branches panel, put your cursor on the branch you want to merge &lt;em&gt;in&lt;/em&gt; (the source branch) and press &lt;code&gt;M&lt;/code&gt;. A panel titled &lt;code&gt;Merge&lt;/code&gt; opens with four options (in lazygit version 0.62.2):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Regular merge (fast forward)&lt;/strong&gt; — &lt;code&gt;m&lt;/code&gt; (fast-forwards when possible, otherwise creates a merge commit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regular merge (with merge commit)&lt;/strong&gt; — &lt;code&gt;n&lt;/code&gt; (always creates a merge commit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Squash merge and leave uncommitted&lt;/strong&gt; — &lt;code&gt;s&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Squash merge and commit&lt;/strong&gt; — &lt;code&gt;S&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa8gbc6pf4w5mououjcck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa8gbc6pf4w5mououjcck.png" alt="The lazygit M panel with the four options" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same panel, four different histories. Let's experiment with each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up a throwaway repo
&lt;/h2&gt;

&lt;p&gt;Everything below reproduces on any machine with git and lazygit installed (&lt;code&gt;brew install lazygit&lt;/code&gt; or &lt;code&gt;apt install lazygit&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /tmp/merge-modes &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/merge-modes
git init &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;--allow-empty&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"init"&lt;/span&gt;

&lt;span class="c"&gt;# a feature branch with three commits&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature
&lt;span class="nb"&gt;echo &lt;/span&gt;one &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; a.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git add &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feature: step one"&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;two &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; b.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git add &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feature: step two"&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;three &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; c.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git add &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feature: step three"&lt;/span&gt;

git checkout main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open lazygit in this repo, go to the branches panel, put the cursor on &lt;code&gt;feature&lt;/code&gt;, and press &lt;code&gt;M&lt;/code&gt;. After each experiment, run &lt;code&gt;git log --oneline --graph&lt;/code&gt; in the shell and look at the shape. Then &lt;code&gt;git reset --hard HEAD@{1}&lt;/code&gt; to try the next option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regular merge (fast forward) — &lt;code&gt;m&lt;/code&gt; option
&lt;/h2&gt;

&lt;p&gt;This is the default in the &lt;code&gt;M&lt;/code&gt; menu, and it behaves differently depending on the state of the target branch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;main&lt;/code&gt; has not moved since you cut &lt;code&gt;feature&lt;/code&gt;, git can fast-forward: &lt;code&gt;main&lt;/code&gt;'s pointer slides up to &lt;code&gt;feature&lt;/code&gt;'s tip. No new commit is created.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;main&lt;/code&gt; has moved on, git cannot fast-forward, and a merge commit is created instead (same shape as the no-fast-forward option we discuss next).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our throwaway repo is in the fast-forwardable state, so pressing &lt;code&gt;m&lt;/code&gt; produces the linear graph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* feature: step three
* feature: step two
* feature: step one
* init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branch itself leaves no trace. That is the nice thing and the cost at the same time: the history looks as one straight line, and nothing in it says "these three commits arrived together as one reviewed unit." Use this option when you have a really short-lived branch and the target branch did not move; the moment the target has its own commits, you get the merge-commit graph instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regular merge (with merge commit) — &lt;code&gt;n&lt;/code&gt; option
&lt;/h2&gt;

&lt;p&gt;This one always produces a merge commit with two parents, regardless of whether the target has moved: the previous tip of &lt;code&gt;main&lt;/code&gt; and the tip of &lt;code&gt;feature&lt;/code&gt;. Press &lt;code&gt;n&lt;/code&gt; when you specifically want the "a branch landed here" marker on the graph. The result looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   merge branch 'feature' into main
|\
| * (feature) feature: step three
| * feature: step two
| * feature: step one
|/
* init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three feature commits survive as themselves, and the merge commit is a permanent marker that says "a branch landed here." That marker is the point of this option. If you ever want to read the history as a series of landed branches, one entry per merge, &lt;code&gt;git log --first-parent&lt;/code&gt; gives you exactly that view, and the merge commit is what makes it work.&lt;/p&gt;

&lt;p&gt;The cost: the graph has merge knots, and anything that assumes a linear history (such as some revert flows) gets slightly more work to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Squash merge — &lt;code&gt;s&lt;/code&gt; and &lt;code&gt;S&lt;/code&gt; options
&lt;/h2&gt;

&lt;p&gt;Both squash options collapse the source branch's commits (the three &lt;code&gt;feature: step&lt;/code&gt; commits) into one new commit on the target. The intermediate commits on the source branch do not appear on the target. Only the one squashed commit does. So &lt;code&gt;git log&lt;/code&gt; on the target cannot show how the source branch evolved, step by step. The trade is: you gain a clean, one-commit-per-change history, but you lose the record of the journey.&lt;/p&gt;

&lt;p&gt;After &lt;code&gt;S&lt;/code&gt;, the target's history looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* squash merge feature into main
* init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;s&lt;/code&gt;, &lt;code&gt;git log&lt;/code&gt; on the target is unchanged from before the merge — the squashed changes land in your working tree, staged but uncommitted. You write the commit message yourself, then commit.&lt;/p&gt;

&lt;p&gt;In practice &lt;code&gt;s&lt;/code&gt; is the one I reach for when the commit message matters, which is most of the time: after the squash, that message is the only record of the branch that survives. &lt;code&gt;S&lt;/code&gt; is the quick path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the shape of a repo's past merges
&lt;/h2&gt;

&lt;p&gt;Once you know the four shapes, you start seeing them in real repos. Run these on any repo with some history (a long-lived side project is a good start):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--merges&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--first-parent&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--merges&lt;/code&gt; shows only merge commits, while &lt;code&gt;--first-parent&lt;/code&gt; walks only the target side of each merge, which is the "one entry per landed change" view. Check the two lists together:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;
&lt;code&gt;--merges&lt;/code&gt; shows&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;--first-parent&lt;/code&gt; shows&lt;/th&gt;
&lt;th&gt;The repo's merge style&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;many merge commits&lt;/td&gt;
&lt;td&gt;each merge is one entry&lt;/td&gt;
&lt;td&gt;merge commits (no-fast-forward, or &lt;code&gt;m&lt;/code&gt; when the target has moved)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;few or no merge commits&lt;/td&gt;
&lt;td&gt;one commit per landed branch, "squash"-style messages&lt;/td&gt;
&lt;td&gt;squash (&lt;code&gt;s&lt;/code&gt; or &lt;code&gt;S&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no merge commits, granular commits&lt;/td&gt;
&lt;td&gt;linear history&lt;/td&gt;
&lt;td&gt;fast-forward (&lt;code&gt;m&lt;/code&gt; when the target is quiet) or direct pushes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I ran these on my own old repos and found exactly what you'd expect from someone who never thought about it: a mix. Squash here, a merge commit there, a fast-forward when the target happened to be quiet. No single shape, because no single decision was ever made. The commands are useful precisely because the shape is usually undocumented — the history is the only record of the choice, whether it was deliberate or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking one and living with it
&lt;/h2&gt;

&lt;p&gt;Pick one shape (and merge option) and keep to it. Six months from now, you may have to revert a bad change or read the history to understand why something was done — in those situations, the job is easier when the history has a consistent shape, and harder when it mixes all four options at random.&lt;/p&gt;

&lt;p&gt;If you pick wrong, the merge commit is just a commit: in the shell, do &lt;code&gt;git reset --hard HEAD@{1}&lt;/code&gt;, or in lazygit, from the commits panel, put the cursor on the bad commit, and press &lt;code&gt;z&lt;/code&gt; to soft-reset. And if the merge produced a conflict, there is no abort path — only forward: lazygit opens the conflict view, you resolve each hunk, do &lt;code&gt;esc&lt;/code&gt; back to the files panel, and the merge flow continues from there.&lt;/p&gt;

&lt;p&gt;That's what is behind lazygit's M panel and the four types of history its options leave. Pick whichever one matches the shape you want your future self to read.&lt;/p&gt;




&lt;p&gt;This article expands issue #7 of my newsletter, &lt;a href="https://www.shellcrafthq.com/" rel="noopener noreferrer"&gt;Shellcraft&lt;/a&gt; — one piece of terminal craft per week.&lt;/p&gt;

</description>
      <category>git</category>
      <category>lazygit</category>
      <category>tutorial</category>
      <category>terminal</category>
    </item>
    <item>
      <title>I Test Every Terminal Claim Before I Publish It</title>
      <dc:creator>Kamon Ayeva</dc:creator>
      <pubDate>Thu, 10 Sep 2026 18:35:51 +0000</pubDate>
      <link>https://dev.to/kamon-shellcraft/i-test-every-terminal-claim-before-i-publish-it-1k83</link>
      <guid>https://dev.to/kamon-shellcraft/i-test-every-terminal-claim-before-i-publish-it-1k83</guid>
      <description>&lt;p&gt;Hello fellow devs.&lt;br&gt;
I'm Kamon Ayeva. I've spent 20+ years building and deploying web applications, and most of that time in production contexts, where the terminal is where you debug, deploy, and fix things under pressure.&lt;/p&gt;

&lt;p&gt;I remember a long time ago when a colleague advised to switch to Linux if I wanted to be more productive. And later, like many developers, I switched to macOS as my OS of choice.&lt;/p&gt;

&lt;p&gt;My avenues of learning and teaching have always been inspired by: productivity, frictionless collaboration, the terminal and the power of the CLI. That's where my writing comes from: craft I picked up, and repeated, because a job needed it.&lt;/p&gt;

&lt;p&gt;I write a weekly newsletter called &lt;a href="https://www.shellcrafthq.com/" rel="noopener noreferrer"&gt;Shellcraft&lt;/a&gt;, one piece of terminal craft per issue: fzf, broot, lazygit, bat, DuckDB, the shell idioms in between, etc. I'm also the author of &lt;a href="https://kamonayeva.gumroad.com/l/modern-cli-stack" rel="noopener noreferrer"&gt;The Modern CLI Stack&lt;/a&gt;, a free book about the modern terminal toolkit.&lt;/p&gt;

&lt;p&gt;This is my first post here, and I want it to introduce the rule behind everything I write, because it's the reason the newsletter is worth your time:&lt;/p&gt;

&lt;p&gt;I run every claim on my own machine before it's published.&lt;/p&gt;

&lt;p&gt;Roughly one in five claims I draft turns out to be wrong when I test it. Not typos, but plausible-sounding errors. A shortcut that the current version doesn't bind anymore. A config format I half-remembered from an older release. A verb that the docs mention but the binary doesn't ship. Last month I drafted an issue about a tool's panel workflow, ran the session walk before publishing, and discovered the step the whole issue was built around didn't happen on my machine. I spent time rewriting the issue around what actually happens, and I was happy with shipping that version.&lt;/p&gt;

&lt;p&gt;The drafts that pass without corrections are the dangerous ones. So before each issue goes out, I write a small verification guide for it (steps to reproduce it, the expected output, etc). It's a private test file, and running it is one of the last steps before publish.&lt;/p&gt;

&lt;p&gt;Why am I telling you this in my intro post here? Because it's the promise I'm making to this community. I plan to post here, at least once a month, expanded versions of what I cover in the newsletter, the same way I write it.&lt;/p&gt;

&lt;p&gt;If that's the kind of thing you want in your feed, the &lt;a href="https://www.shellcrafthq.com/" rel="noopener noreferrer"&gt;newsletter&lt;/a&gt; sends it weekly. The &lt;a href="https://kamonayeva.gumroad.com/l/modern-cli-stack" rel="noopener noreferrer"&gt;book&lt;/a&gt; is the free full toolkit.&lt;/p&gt;

&lt;p&gt;Happy to be here.&lt;br&gt;
What's a terminal claim you believed for years that turned out to be false? Or a suggestion for something I should feature in the newsletter?&lt;/p&gt;

</description>
      <category>terminal</category>
      <category>cli</category>
      <category>devops</category>
      <category>shell</category>
    </item>
  </channel>
</rss>
