<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>sglavoie.com – Category: Workflow</title><link>https://www.sglavoie.com/categories/workflow/</link><description>Posts and writings by Sébastien Lavoie. Customization setups and articles on productivity for developers who like minimalism.</description><language>en</language><atom:link href="https://www.sglavoie.com/categories/workflow/feed.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Fri, 09 Oct 2026 16:20:00 -0600</lastBuildDate><item><title>From a plan to unattended agent runs: how I drive coding agents one session at a time</title><link>https://www.sglavoie.com/posts/from-plan-to-unattended-agent-runs/</link><description>Long conversations with a coding agent drift, and “done” too often means “the agent said so”. This is the pipeline I built to turn a plan into small sessions with checks the agent can’t talk its way around — a continuation of the same instinct behind systematic reviews and parallel work with Git worktrees.</description><content:encoded>&lt;p&gt;&lt;em&gt;Written with AI: This post was drafted with an AI assistant, working from my notes, code and tools, then reviewed and edited by me. The experiences and opinions are mine, and so are any mistakes.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Coding agents got good enough, quickly enough, that the bottleneck in my day moved. It&amp;rsquo;s no longer typing the code; it&amp;rsquo;s making sure the code that gets typed is the code I wanted, and that it actually works. The first few weeks went the way I suspect they go for a lot of people: one long conversation per feature, a growing pile of context, and a moment near the end where the agent cheerfully announces it&amp;rsquo;s done and I discover, ten minutes later, that &lt;em&gt;&amp;ldquo;done&amp;rdquo;&lt;/em&gt; meant &lt;em&gt;&amp;ldquo;the parts I remembered are done&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Trying to resume progress on large features led me to try different frameworks such as &lt;a href="https://github.com/github/spec-kit"&gt;Spec Kit&lt;/a&gt; from GitHub, &lt;a href="https://github.com/Fission-AI/openspec"&gt;OpenSpec&lt;/a&gt;, the &lt;a href="https://kiro.dev/"&gt;Kiro IDE&lt;/a&gt;, standalone Claude skills and so on. I even built my own harness-agnostic spec-driven framework which I used for a while with (slow) success with GitHub Copilot CLI, Codex and Claude Code. That level of ceremony was right for less capable models, but the picture evolved: the models got better at producing what I wanted without being walked through every step, and I found I needed a different kind of help.&lt;/p&gt;
&lt;p&gt;What I needed wasn&amp;rsquo;t more guidance for the agent. It was less trust in it. Generated code reads fluently, compiles, and is sometimes wrong in ways that only show up later: a concurrency guard that guards nothing, a test that faithfully asserts the bug. &lt;strong&gt;Fluency and correctness are uncorrelated&lt;/strong&gt;, and anyone who has reviewed enough agent output has caught that gap personally. Two problems kept coming back:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Long conversations drift.&lt;/strong&gt; The further a session goes, the more the early decisions fade, and the agent starts re-solving problems it already solved, or quietly undoing them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Done&amp;rdquo; was decided by the agent.&lt;/strong&gt; There was no external definition of done that the agent couldn&amp;rsquo;t argue with, so every finished session still needed me to establish, from scratch, whether it was actually finished.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The second one matters more than it looks. When an agent writes the code, the review doesn&amp;rsquo;t go away; it moves, and it gets harder, because I&amp;rsquo;m now checking work I didn&amp;rsquo;t build the understanding for. Review is the scarce resource. Anything that makes the agent produce more code without making that code cheaper to verify just moves the bottleneck around.&lt;/p&gt;
&lt;p&gt;What I ended up with is a small (well&amp;hellip; not so small anymore) tool called &lt;code&gt;asl&lt;/code&gt;, short for &lt;em&gt;agent session loop&lt;/em&gt;. The idea is simple: write a plan, cut it into sessions that each fit comfortably in one fresh conversation, attach to every session a check that a program — not the agent — runs, and then let a runner go through the sessions one at a time while I do something else. This post walks through that pipeline, why each step exists, and what I learned building it.&lt;/p&gt;
&lt;h2 id="why-plans-not-prompts"&gt;Why plans, not prompts&lt;/h2&gt;
&lt;p&gt;Most of the leverage happens before any code is written. A vague prompt to an agent yields a confident implementation of something adjacent to what you wanted; a precise plan yields something much closer. So the pipeline starts with a plain Markdown plan, and with a step whose entire job is to make that plan better before anything runs.&lt;/p&gt;
&lt;p&gt;The bar I hold a plan to is that it should be &lt;em&gt;decision-complete&lt;/em&gt;: every choice the implementer shouldn&amp;rsquo;t be making on their own has already been made. The test I use is a slightly hostile question: &lt;strong&gt;could two competent implementers follow this plan and end up with meaningfully different behaviour?&lt;/strong&gt; If the answer is yes, the plan is hiding a decision — about an API, a data format, an error case, a migration order — and the agent will happily make that decision for me, silently, somewhere in the middle of a diff.&lt;/p&gt;
&lt;p&gt;It helps to separate facts from decisions. &amp;ldquo;Which callers use this function?&amp;rdquo; is something the agent can discover by reading the repository, and it should. &amp;ldquo;Which of those callers are we willing to break?&amp;rdquo; is a judgement call, and it stays with me. A good plan records the first kind as discovery and the second kind as an explicit decision.&lt;/p&gt;
&lt;p&gt;The step that enforces this is a &lt;a href="https://docs.claude.com/en/docs/claude-code/skills"&gt;Claude Code skill&lt;/a&gt; called &lt;code&gt;plan-iterate&lt;/code&gt;. It reads the plan, assesses how complete it is (scope, interfaces, tests, risks), asks me targeted questions about the weakest areas, and edits my answers back into the file. With &lt;code&gt;--ideal&lt;/code&gt;, it first does one research pass over the codebase and describes what the &lt;em&gt;unconstrained&lt;/em&gt; ideal version of the change would look like, then ranks the gaps between my plan and that ideal by impact, and asks me to adopt, defer or reject each one. Deferred items get written into the plan so the decision is recorded rather than forgotten.&lt;/p&gt;
&lt;p&gt;This is the same instinct behind &lt;a href="https://www.sglavoie.com/posts/systematic-reviews/"&gt;systematic reviews&lt;/a&gt;: it&amp;rsquo;s cheaper to catch a bad idea on paper than in a diff.&lt;/p&gt;
&lt;h2 id="the-pipeline-step-by-step"&gt;The pipeline, step by step&lt;/h2&gt;
&lt;p&gt;Here is the whole flow, from a plan to a finished run:&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0046_from_plan_to_unattended_agent_runs/pipeline.svg"
 alt="Flowchart of the pipeline. PLAN.md is refined by plan-iterate, then asl prepare runs session-split, session-expand, asl baseline and asl preflight. Red or yellow preflight findings go through plan-preflight-iterate and back to preflight; an all-green plan goes to asl run, which runs one session per fresh agent turn. Python runs each session’s Verify checks: a failure triggers a repair turn with the failing evidence, a pass moves to the next pending session, and the run ends as all-complete. A blocked, unsafe or guard-violating turn, or an exhausted repair budget, pauses the run with a named reason."
 loading="lazy"
 decoding="async"/&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://www.sglavoie.com/files/posts/0046_from_plan_to_unattended_agent_runs/pipeline.txt"&gt;Mermaid source of this diagram&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The steps in the middle used to be separate commands I had to remember to run in order. These days a single &lt;code&gt;asl prepare PLAN.md --cwd REPO_ROOT&lt;/code&gt; runs split, validation, expansion, baseline and the readiness gate deterministically and, on success, prints the exact &lt;code&gt;asl run&lt;/code&gt; command to use next.&lt;/p&gt;
&lt;h3 id="splitting-where-to-cut"&gt;Splitting: where to cut&lt;/h3&gt;
&lt;p&gt;Splitting turns one plan into a directory: a &lt;code&gt;manifest.md&lt;/code&gt; listing the sessions and their readiness state, plus one &lt;code&gt;session-&amp;lt;id&amp;gt;.md&lt;/code&gt; per session. Each session has a scope, the files it&amp;rsquo;s expected to touch, a handoff section the agent fills in when it&amp;rsquo;s done, and a completion contract (more on that below).&lt;/p&gt;
&lt;p&gt;Where to cut took me a while to get right. A good session has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One primary outcome&lt;/strong&gt;, with evidence that can fail on its own. If I can&amp;rsquo;t write a check that distinguishes &amp;ldquo;this session worked&amp;rdquo; from &amp;ldquo;this session didn&amp;rsquo;t&amp;rdquo;, it isn&amp;rsquo;t a session yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A useful state if the later sessions never run.&lt;/strong&gt; If the run stops after session 3, sessions 1 to 3 should still leave the codebase in a coherent, reviewable state, not half of a refactor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Room to work in one fresh context.&lt;/strong&gt; Context is a split criterion in its own right: the files involved, their size, the instructions that apply and the handoffs from earlier sessions all need to fit, with space left over for the agent to think. It&amp;rsquo;s much cheaper to re-split before running than to discover forty minutes in that a session can&amp;rsquo;t hold everything it needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two anti-patterns I now avoid on purpose: splitting implementation from its tests (the test session ends up testing whatever the implementation happened to do), and a &amp;ldquo;cleanup later&amp;rdquo; session whose absence would leave the change unsafe. Sessions are ordered by dependency, not by whatever is easiest to edit first: shared contracts and scaffolding before their consumers, additions before removals.&lt;/p&gt;
&lt;h3 id="expanding-the-plan-is-a-set-of-claims"&gt;Expanding: the plan is a set of claims&lt;/h3&gt;
&lt;p&gt;Every path, function signature, command and test name in a plan is a &lt;em&gt;claim&lt;/em&gt; about the repository, and plans are often written against a mental model that&amp;rsquo;s a few commits out of date. Expanding is where the agent reads the actual codebase and checks each of those claims: do these files exist, do these functions have the signatures the plan assumes, is this session secretly three sessions?&lt;/p&gt;
&lt;p&gt;The rule that makes this useful is that &lt;strong&gt;corrections go into the plan, not into the implementation.&lt;/strong&gt; Problems get written into a &lt;code&gt;## Contract Issues&lt;/code&gt; section of the session rather than silently patched around. If the agent compensates for a stale fact while implementing, the only record of that decision is the diff, and the next session inherits the stale fact. A session that turns out to be too big can be re-split into child sessions with &lt;code&gt;session-resplit&lt;/code&gt; while the rest of the plan is left untouched.&lt;/p&gt;
&lt;h3 id="preflight-the-gate"&gt;Preflight: the gate&lt;/h3&gt;
&lt;p&gt;Preflight produces red, yellow and green findings, each with a machine-readable repair hint that names the failing file, the check that failed and the smallest repair. A red or yellow finding blocks the plan from becoming &lt;code&gt;loop-ready&lt;/code&gt;, and &lt;code&gt;asl run&lt;/code&gt; refuses to start on anything else. When there are findings, a companion skill, &lt;code&gt;plan-preflight-iterate&lt;/code&gt;, walks through them with me as a short Q&amp;amp;A and reruns preflight until it&amp;rsquo;s clean. Because each finding names its smallest repair, that pass only touches what broke.&lt;/p&gt;
&lt;p&gt;Preflight also checks the contracts themselves: every expected file either exists or is explicitly marked as new, every command is available, and no placeholder or open decision survived into a session.&lt;/p&gt;
&lt;p&gt;One design rule from this part spread to the whole tool: &lt;strong&gt;keep authored state separate from derived state.&lt;/strong&gt; The plan, the sessions, the decisions and the handoffs are authored; the manifest&amp;rsquo;s readiness flags and status tables are derived, and get rebuilt from the authored files. When the two disagree, the tool pauses rather than picking a winner. A readiness table can always be regenerated; a decision that got silently overwritten can&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;The point of all this ceremony is to move failures earlier. A missing file discovered during preflight costs a few seconds; the same missing file discovered forty minutes into a run costs a confused agent, a half-finished session and a messy diff.&lt;/p&gt;
&lt;h2 id="completion-contracts-who-decides-what-done-means"&gt;Completion contracts: who decides what &amp;ldquo;done&amp;rdquo; means&lt;/h2&gt;
&lt;p&gt;This is the part I&amp;rsquo;d keep if I had to throw everything else away.&lt;/p&gt;
&lt;p&gt;Every session file ends with a block like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;markdown&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Completion Contract
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;### Done when
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; The &lt;span class="sb"&gt;`export`&lt;/span&gt; command writes one JSON file per project.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Existing CLI tests still pass.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;### Verify
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;session&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;02&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;checks&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;unit-suite&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;argv&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-m&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;unittest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-q&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;timeout_seconds&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&amp;ldquo;Done when&amp;rdquo; is for humans (and for the agent to aim at). &amp;ldquo;Verify&amp;rdquo; is for the runner: a list of checks — commands to run, files that must exist, patterns that must appear in a file — that the Python runner executes after the agent says it&amp;rsquo;s finished. If a check fails, the session isn&amp;rsquo;t complete, no matter how convincing the agent&amp;rsquo;s summary was. Commands are given as an argument list and run without a shell, so there&amp;rsquo;s no quoting to get subtly wrong.&lt;/p&gt;
&lt;p&gt;Writing these contracts taught me that a check is only as good as what it can &lt;em&gt;falsify&lt;/em&gt;. A test suite that would also pass for a plausible wrong implementation isn&amp;rsquo;t evidence of anything, whoever wrote the code. Agents make that old truth urgent, because they produce plausible wrong implementations at volume. So before I accept a contract, I try to describe one implementation that would be wrong but would still pass it. If that&amp;rsquo;s easy, the contract needs a sharper check, or the session is claiming more than it can prove.&lt;/p&gt;
&lt;p&gt;Three details make this more robust than it sounds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Baselines.&lt;/strong&gt; Before the run, &lt;code&gt;asl baseline&lt;/code&gt; executes every contract and records what it reports on the unchanged codebase, and preflight won&amp;rsquo;t let a plan start without that evidence. A check can declare how it should behave before any work happens: a reproducer is &lt;em&gt;expected to fail&lt;/em&gt; (proving it actually reaches the thing being changed), while protected behaviour is &lt;em&gt;expected to pass&lt;/em&gt; throughout. A protected check that already fails is a broken contract, not a goal — and a reproducer that already passes means the session is either done or testing the wrong thing. Either way, better to know up front than to blame the session for a pre-existing red.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Every check backs a named claim, with a limit.&lt;/strong&gt; &amp;ldquo;The focused test passes&amp;rdquo; says nothing about the database, the deployed service or the code paths it doesn&amp;rsquo;t exercise. Keeping that limit written next to the check stops a green session from quietly turning into &amp;ldquo;it works&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The split between judgement and enforcement.&lt;/strong&gt; The agent decides &lt;em&gt;how&lt;/em&gt; to implement a session. Python decides whether it&amp;rsquo;s &lt;em&gt;done&lt;/em&gt;. The skill the agent runs for each session, &lt;code&gt;session-run&lt;/code&gt;, is under a hundred lines of instructions, and that&amp;rsquo;s on purpose: when the runner launches it, Python already holds the plan lock and has checked readiness, and after the agent returns, Python runs the verification, syncs the progress journal and refreshes readiness. Nothing that matters is left for the model to remember.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point turned into a design rule for everything else: &lt;strong&gt;anything that must happen is code; only things that require judgement go in a prompt.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="what-a-run-looks-like"&gt;What a run looks like&lt;/h2&gt;
&lt;p&gt;Starting a run looks like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;asl run ~/.local/share/agent-plans/export-command/ --harness claude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The runner then loops:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Re-read the plan files from disk.&lt;/li&gt;
&lt;li&gt;Pick the first pending session.&lt;/li&gt;
&lt;li&gt;Start one agent turn, in a fresh context, running &lt;code&gt;/session-run&lt;/code&gt; on the plan.&lt;/li&gt;
&lt;li&gt;When the turn ends, run the session&amp;rsquo;s Verify checks.&lt;/li&gt;
&lt;li&gt;If they pass, mark the session complete and go back to step 1. If not, put the session back to pending and hand the failing evidence to a repair turn; when the repair budget runs out, stop with a reason.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Re-reading from disk on every turn matters more than it looks. The plan directory is the single source of truth, so I can edit a future session while a run is in progress, and the next turn picks up the change. Nothing about the plan lives only in the runner&amp;rsquo;s memory.&lt;/p&gt;
&lt;h3 id="fresh-context-durable-handoffs"&gt;Fresh context, durable handoffs&lt;/h3&gt;
&lt;p&gt;Every session starts in a brand new conversation, loaded with only what it needs: the instructions that apply, the plan, its own session file and contract, and the handoffs from the sessions it depends on. That&amp;rsquo;s the fix for drift: there&amp;rsquo;s no long transcript for early decisions to fade out of.&lt;/p&gt;
&lt;p&gt;It also means the handoff has to carry everything. When a session finishes, the agent fills in its handoff: the files it modified, the key decisions it made, anything blocking the next session, and notes such as why it reran a check. Two optional sections are worth singling out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Discovered facts&lt;/strong&gt; record what implementation proved about the plan&amp;rsquo;s assumptions (&amp;ldquo;the config loader already caches; no need for a second layer&amp;rdquo;), so the next session doesn&amp;rsquo;t spend its context re-researching a settled question.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open questions&lt;/strong&gt; hand forward what&amp;rsquo;s still uncertain, named, instead of leaving it to be rediscovered.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My test for a handoff is blunt: if losing the transcript changes what the next session is allowed to do, the handoff is incomplete. Conversations are disposable; the plan directory is the memory.&lt;/p&gt;
&lt;h3 id="pausing-is-a-feature"&gt;Pausing is a feature&lt;/h3&gt;
&lt;p&gt;Agents fail in loops more than in crashes: rereading the same files, rerunning the same green check, restating the same hypothesis as a &amp;ldquo;new&amp;rdquo; fix. Left alone, an unattended run will happily burn an hour that way. So a session that can&amp;rsquo;t make progress doesn&amp;rsquo;t keep trying; it pauses, and a pause is a durable state transition with a stable reason, not an abandoned conversation.&lt;/p&gt;
&lt;p&gt;The vocabulary is deliberately small and fixed: &lt;code&gt;blocked&lt;/code&gt; for a decision only I can make, &lt;code&gt;unsafe&lt;/code&gt; when continuing could do damage, &lt;code&gt;verify-failed&lt;/code&gt; when the contract didn&amp;rsquo;t pass after repairs, &lt;code&gt;no-contract&lt;/code&gt; or &lt;code&gt;malformed-contract&lt;/code&gt; when there&amp;rsquo;s nothing trustworthy to verify against, a guard violation, and so on. The agent isn&amp;rsquo;t allowed to invent new reasons. Each pause records what blocked it and the evidence, so the next step is obvious. That makes &amp;ldquo;why did it stop?&amp;rdquo; a question with an answer, and it means a retry has to come with something new — a changed hypothesis, a fixed fixture, a decision — rather than the same attempt with more hope.&lt;/p&gt;
&lt;p&gt;It took me a while to stop treating pauses as failures. &lt;strong&gt;A necessary pause is the system working.&lt;/strong&gt; A run that pushes through an unresolved decision to keep its completion rate up is worse than one that stops and asks.&lt;/p&gt;
&lt;h3 id="the-controls-i-use-all-the-time"&gt;The controls I use all the time&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;--harness&lt;/code&gt; picks the agent: Claude Code, Codex or Copilot CLI. The same plan runs on any of them, and I can switch mid-run for the remaining sessions with &lt;code&gt;asl control select-harness&lt;/code&gt;. A few other controls I use all the time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;asl control stop-after-current&lt;/code&gt;&lt;/strong&gt; lets the current session finish and then exits cleanly, so the next &lt;code&gt;asl run&lt;/code&gt; picks up where it stopped. It&amp;rsquo;s the polite version of Ctrl-C.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--max-sessions N&lt;/code&gt;&lt;/strong&gt; runs at most &lt;code&gt;N&lt;/code&gt; sessions and stops successfully. Useful when I want to look at the first session&amp;rsquo;s output before trusting the rest.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pauses with questions.&lt;/strong&gt; If an agent hits a decision it shouldn&amp;rsquo;t make alone, the session pauses with &lt;code&gt;reason: blocked&lt;/code&gt;. &lt;code&gt;asl questions PLAN_DIR&lt;/code&gt; renders the questions, and &lt;code&gt;asl run PLAN_DIR --reply &amp;quot;...&amp;quot;&lt;/code&gt; resumes the paused turn with my answer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--review-harness codex&lt;/code&gt;&lt;/strong&gt; adds a final session where a &lt;em&gt;different&lt;/em&gt; agent reviews the finished work and writes its findings to a &lt;code&gt;final-review.md&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="a-second-agent-reviewing-cold"&gt;A second agent, reviewing cold&lt;/h3&gt;
&lt;p&gt;That last control deserves a little more space. The reviewer runs on a different harness from the implementer, isn&amp;rsquo;t allowed to change any code, and reviews &lt;em&gt;cold&lt;/em&gt;: from the plan, the contracts and the actual changes, not from the implementer&amp;rsquo;s account of them. Its job is to find where the change violates the intent, the protected behaviour, the scope or the limits of its own evidence. Its output is structured, not &amp;ldquo;looks good&amp;rdquo;: every finding has a severity, the files it points at, an explanation, what &amp;ldquo;fixed&amp;rdquo; means, and the checks that would prove it, and the run refuses a review whose summary line and findings don&amp;rsquo;t agree.&lt;/p&gt;
&lt;p&gt;Repairs for its findings are bounded too. Findings are grouped, each group gets a capped number of repair attempts, and the review-and-repair cycles themselves are capped, so a reviewer and an implementer can&amp;rsquo;t ping-pong forever. The principle I hold myself to on top of that: a repair fixes the finding inside the reviewed boundary, and anything that would change the design goes back to the plan as a new session instead of being absorbed into the fix. The cap limits rework; it never lowers the bar for acceptance.&lt;/p&gt;
&lt;p&gt;Having one model check another&amp;rsquo;s work catches a surprising number of things, but I try not to over-read it. A different model brings different habits for finding failures, not independent evidence. What actually decides acceptance is still the checks, the reproducers and me reading the diff.&lt;/p&gt;
&lt;!-- TODO(seb): an example of a review finding from a second harness that the first one missed. --&gt;
&lt;p&gt;When the run ends, it ends with an explicit reason: &lt;code&gt;all-complete&lt;/code&gt;, &lt;code&gt;max-sessions&lt;/code&gt; or &lt;code&gt;stopped&lt;/code&gt; for the happy paths, or a named pause like a failing check or a guard violation. There is no &amp;ldquo;it just stopped&amp;rdquo;. And &amp;ldquo;all-complete&amp;rdquo; still means &lt;em&gt;the contracts passed&lt;/em&gt;, not &lt;em&gt;the change is correct&lt;/em&gt;: it&amp;rsquo;s the point where I go and read the diff, the handoffs and the review, not the point where I merge.&lt;/p&gt;
&lt;h2 id="guardrails-briefly"&gt;Guardrails, briefly&lt;/h2&gt;
&lt;p&gt;Once a run is unattended, you need a way to steer it, and the most important distinction I ended up drawing is between &lt;em&gt;advice&lt;/em&gt; and &lt;em&gt;enforcement&lt;/em&gt;. Telling an agent not to deploy is steering; making sure it can&amp;rsquo;t reach the deploy credentials is a boundary. A prompt expresses intent, never authority.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Guidance is advice.&lt;/strong&gt; I can send a running session a note (&amp;ldquo;prefer the existing date helper over adding a dependency&amp;rdquo;) and it&amp;rsquo;s delivered into the conversation. It can shape implementation choices, but it can&amp;rsquo;t change the session&amp;rsquo;s scope or its completion contract.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Only a guard enforces.&lt;/strong&gt; &lt;code&gt;asl control guard add&lt;/code&gt; can deny a command (matched as an exact argument prefix and enforced by a per-run &lt;code&gt;PATH&lt;/code&gt; shim) or protect a path (compared against a snapshot taken at the start of the turn). A violation rejects the turn and pauses the run. It never rolls anything back, so I can always see exactly what the agent did.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A guard also reports, per harness, whether it is &lt;em&gt;active&lt;/em&gt;, &lt;em&gt;degraded&lt;/em&gt; or &lt;em&gt;unsupported&lt;/em&gt;. Not every agent CLI exposes the same hooks, and I&amp;rsquo;d rather have a guard say &amp;ldquo;I can&amp;rsquo;t enforce this here&amp;rdquo; than quietly imply it did.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s more to supervising a run than this, like a mailbox that lets a running agent ask questions without stopping, and a separate &amp;ldquo;overseer&amp;rdquo; that watches runs across projects. They all follow the same rule: an AI that can be argued into an action must never be the guard.&lt;/p&gt;
&lt;h2 id="when-this-is-overkill"&gt;When this is overkill&lt;/h2&gt;
&lt;p&gt;Most changes don&amp;rsquo;t need any of this. A local, reversible edit with a test that already covers it needs a boundary, a check and a review — that&amp;rsquo;s it. I reach for a full plan and an unattended run when a change spans several sessions&amp;rsquo; worth of work, when the order of the steps matters, or when I want the run to make progress while I&amp;rsquo;m doing something else. The pipeline exists so every decision has a home when a change is big enough to need one, not so every typo fix carries maximum ceremony.&lt;/p&gt;
&lt;p&gt;A quick way to tell: before handing a task to an agent, can I state the observable outcome and one behaviour that must not change, bound the files involved, name a check that would fail for a plausible wrong implementation, and point to someone (usually me) who can actually absorb the diff? If one of those answers is &amp;ldquo;no&amp;rdquo; or &amp;ldquo;not sure&amp;rdquo;, the right next step is usually a smaller precursor — reproduce the bug, inventory the callers, write the failing test — rather than the whole change.&lt;/p&gt;
&lt;h2 id="lessons-from-four-months-of-building-it"&gt;Lessons from four months of building it&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;asl&lt;/code&gt; started as a small loop in a script in early June 2026. Four months later it&amp;rsquo;s about 180,000 lines of standard-library-only Python with about 270,000 lines of tests, built largely by running plans through the tool itself. A few things I&amp;rsquo;d tell myself at the start:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;More tests than code is not a vanity metric.&lt;/strong&gt; When most of the code is written by agents, the tests are the specification, and they&amp;rsquo;re what makes the next change safe. There is also a &lt;code&gt;reliability-stress&lt;/code&gt; suite that reruns timing-sensitive scenarios under load and across time zones, because &amp;ldquo;it passed on my machine at 3 p.m.&amp;rdquo; is not good enough for something that runs unattended overnight.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make failures early and loud.&lt;/strong&gt; Every hour spent on preflight, baselines and repair hints has paid itself back many times over in runs that didn&amp;rsquo;t die halfway.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Promote automation by maturity, not enthusiasm.&lt;/strong&gt; Almost every piece of &lt;code&gt;asl&lt;/code&gt; went through the same ladder: do it by hand while watching, write it down as a checklist or skill, script the deterministic parts, and only then enforce it in the runner. Skipping a rung always produced something that failed in a way I didn&amp;rsquo;t yet understand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Turn each run failure into a control.&lt;/strong&gt; The best features came from runs that went wrong. Agents pausing the whole run for every small question became a live question-and-reply channel; answers that were accepted but never actually read by the agent became receipts proving the agent consumed them. Each one started as a reproducer from a real run and landed as its own reviewed change — never as a quick patch in the middle of the run that exposed it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write down the reason for every stop.&lt;/strong&gt; Every pause and every exit has a stable, named reason. That made the tool scriptable, and made it possible to build a supervisor on top of it at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t trust a harness to enforce what it can&amp;rsquo;t.&lt;/strong&gt; Supporting three agent CLIs taught me that they differ a lot in what they can actually guarantee, and that the honest answer is to report capabilities instead of assuming them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Be suspicious of feeling faster.&lt;/strong&gt; A &lt;a href="https://arxiv.org/abs/2507.09089"&gt;2025 randomized trial by METR&lt;/a&gt; found experienced open-source maintainers were about 19% &lt;em&gt;slower&lt;/em&gt; with early-2025 AI tooling on their own repositories, while believing they&amp;rsquo;d been faster. That&amp;rsquo;s not a verdict on today&amp;rsquo;s tools, but it&amp;rsquo;s a good reason to judge a workflow by what passes review rather than by how productive a session felt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standard library only has been worth it.&lt;/strong&gt; No dependency upgrades, no virtual environment to break, and the whole thing installs by symlinking a directory with GNU Stow, alongside the rest of &lt;a href="https://www.sglavoie.com/posts/managing-dotfiles-with-git-bare-repository/"&gt;my dotfiles&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- TODO(seb): what broke along the way, and what held up better than expected. --&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The biggest change wasn&amp;rsquo;t any single feature; it was moving &amp;ldquo;done&amp;rdquo; out of the agent&amp;rsquo;s hands. Once every session has a contract that a program checks, the rest follows naturally: sessions can be small and start fresh, runs can be unattended, pauses become information instead of mysteries, a second agent can review the first one&amp;rsquo;s work, and I can spend my attention on the plan and the review instead of on babysitting a conversation.&lt;/p&gt;
&lt;p&gt;If you work with coding agents a lot, my advice is to start there, and to start small. Pick one bounded task you&amp;rsquo;d normally do yourself in under an hour. Before handing it over, write down what &amp;ldquo;done&amp;rdquo; means and one check that would fail if the agent got it plausibly wrong. Then review the diff against that, not against the agent&amp;rsquo;s summary. If it doesn&amp;rsquo;t hold up, you&amp;rsquo;ve lost one session and gained a concrete reason; if it does, you have your first contract.&lt;/p&gt;
&lt;h2 id="resources-and-references"&gt;Resources and references&lt;/h2&gt;
&lt;h3 id="external-links"&gt;External links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.claude.com/en/docs/claude-code/skills"&gt;Agent Skills - Claude Code docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.claude.com/en/docs/claude-code/hooks"&gt;Hooks - Claude Code docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2507.09089"&gt;Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity (METR)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Secure_Coding_with_AI_Cheat_Sheet.html"&gt;OWASP Secure Coding with AI Cheat Sheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/stow/"&gt;GNU Stow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="from-this-website"&gt;From this website&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.sglavoie.com/posts/systematic-reviews/"&gt;Systematic reviews&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sglavoie.com/posts/git-worktrees-for-a-better-parallel-workflow/"&gt;Git worktrees for a better parallel workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sglavoie.com/posts/managing-dotfiles-with-git-bare-repository/"&gt;Managing dotfiles with a Git bare repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Fri, 09 Oct 2026 16:20:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/from-plan-to-unattended-agent-runs/</guid><category>workflow</category><category>ai</category><category>agents</category><category>automation</category><category>claude-code</category><category>productivity</category><category>python</category></item><item><title>Systematic reviews</title><link>https://www.sglavoie.com/posts/systematic-reviews/</link><description>I find learning about productivity fascinating, yet I’m aware that in the end, a simple set of rules and systems is what works best for me. These systems will continue to evolve over time, but I have found that the simpler they are, the more likely I am to stick with them. This post describes my current digital take on dealing with everyday life.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I used to think that merely using better or more sophisticated tools would magically make me more productive, blaming external factors and technology on my own lack of ability to achieve my dreams. But I&amp;rsquo;ve come to realize that it was only an escape into the procrastination playground to avoid facing hard work. Working &lt;em&gt;on&lt;/em&gt; a system is not the same as working &lt;em&gt;in&lt;/em&gt; a system. The former is a form of procrastination, while the latter is the actual work that needs to be done.&lt;/p&gt;
&lt;p&gt;While there are great approaches including &lt;a href="https://es.wikipedia.org/wiki/Zettelkasten"&gt;Zettelkasten&lt;/a&gt; and the &lt;a href="https://gettingthingsdone.com/"&gt;Getting Things Done&lt;/a&gt; methodology, I have found that I work optimally with simpler systems that are easier to maintain, which reduce cognitive load and friction (less to think about and no complicated setup when inputting information either). I used to be an avid user of &lt;a href="https://todoist.com/"&gt;Todoist&lt;/a&gt; where I would implement GTD almost as a pure devotee, making the most of all its (relatively) advanced features, but that was a double-edged sword as dealing with timezone and synchronization issues was more than enough to derail me from the path, sometimes with dire repercussions when tasks would suddenly disappear or be scheduled at the wrong time.&lt;/p&gt;
&lt;p&gt;Needless to say, I have since moved on to a simpler system that is more robust and that I can trust. I am not technologically turning back, but I have found &lt;a href="https://todoist.com/productivity-methods/weekly-review"&gt;Todoist&amp;rsquo;s weekly review process&lt;/a&gt; to be quite effective and easy to follow, so I have decided to adopt it as a basis for my own system, which I will be describing briefly in this post.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="a-whirlwind-tour-of-the-system"&gt;A whirlwind tour of the system&lt;/h2&gt;
&lt;p&gt;What follows is nothing revolutionary, but the simplicity of it just stuck with me. From prior research on the topic as I was &lt;a href="https://github.com/sglavoie/cm3070-final-project"&gt;building a todo application&lt;/a&gt;, it really stood out to me that the most effective systems were the simplest ones, especially when the different components can be neatly decoupled from each other while working well together as a whole. Arguably, the Apple ecosystem has done a great job at this &amp;ndash; although I&amp;rsquo;m still enthusiastic about creating a single software destination to manage one&amp;rsquo;s life even more efficiently ;).&lt;/p&gt;
&lt;p&gt;In reality, I just stuck with the default offerings for maximum compatibility and ease of use, but the truth is that the existing applications I have been in touch with do not fully address all my needs. As I&amp;rsquo;m not willing to try to bend reality to my will, I have decided to bend my will to reality for the time being and work with what I have. But I digress.&lt;/p&gt;
&lt;div class="image"&gt;
&lt;img src="https://www.sglavoie.com/images/posts/0037_systematic_reviews/overviewReview.svg" alt="Overview of flow for systematic reviews" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://www.sglavoie.com/files/posts/0037_systematic_reviews/systematic_reviews_overview.plantuml"&gt;PlantUML source of this diagram&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So the main idea here is very simple: twice a day, a daily review occurs and once a week, a more thorough weekly review is performed. Then, at the end of every month and every year, an even more thorough exploration happens both to reflect on the past and plan for the future.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="daily-review"&gt;Daily review&lt;/h2&gt;
&lt;div class="image"&gt;
&lt;img style="max-width: 60%" src="https://www.sglavoie.com/images/posts/0037_systematic_reviews/dailyReview.svg" alt="Flow for systematic daily reviews" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://www.sglavoie.com/files/posts/0037_systematic_reviews/systematic_reviews_daily.plantuml"&gt;PlantUML source of this diagram&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The purpose behind the daily review is twofold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To make sure that the day is planned out and that the most important tasks are completed.&lt;/li&gt;
&lt;li&gt;To do a mindful check on one&amp;rsquo;s wellbeing and re-adjust as needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Point &lt;em&gt;1.&lt;/em&gt; is self-explanatory. It serves as a good reminder that task estimation skills are not perfect and that one should not be too hard on oneself if things do not go as planned. Point &lt;em&gt;2.&lt;/em&gt; is a bit more subtle, but it is important to frequently take a step back and reflect on one&amp;rsquo;s current state of mind and body. That may include taking a break, going for a walk, or even taking a nap. The point is to be mindful of one&amp;rsquo;s current state and to take action to improve it if needed. In that department, I like to exercise early in the morning, both to expose my eyes to sunlight and to get my blood flowing (more on this in the excellent &lt;a href="https://hubermanlab.com/"&gt;Huberman Lab podcast&lt;/a&gt;!). Besides sleeping well and eating healthy, I have found that this is the best way to ensure that I am in a good state of mind to tackle the day&amp;rsquo;s challenges.&lt;/p&gt;
&lt;h2 id="make-relevant-context-accessible"&gt;Make relevant context accessible&lt;/h2&gt;
&lt;p&gt;One thing I like to do to stay on top of priorities is to &amp;ldquo;pin&amp;rdquo; a couple of active notes I&amp;rsquo;m using from Apple Notes directly inside the list of widgets shown when swiping left with two fingers on the touch pad (or clicking on the clock in the top-right corner). There, I quickly see notes like &lt;strong&gt;Working notes&lt;/strong&gt; (to accompany the projects I&amp;rsquo;m currently working on), &lt;strong&gt;Standup&lt;/strong&gt; so I can share updates with my team, &lt;strong&gt;@next_action&lt;/strong&gt; which contains a list of stuff I plan to work on next without having to clutter my todo app and another one such as &lt;strong&gt;Ideas&lt;/strong&gt; which I use as a scratchpad to empty my mind of any thoughts that might be distracting me from the task at hand or that might be useful later on.&lt;/p&gt;
&lt;div class="image"&gt;
&lt;img style="max-width: 670px" src="https://www.sglavoie.com/images/posts/0037_systematic_reviews/apple_widgets.png" alt="Apple widgets, shown when swiping left with the touch pad" /&gt;
&lt;/div&gt;
&lt;p&gt;Another major component of the way I organize tasks (currently with Apple Reminders) is to rely on smart lists and split them by areas of responsibilities. So while I may create tasks under specific lists, I tend to simply schedule everything that&amp;rsquo;s either recurring or that will need attention in the near future, storing the remaining ideas and potential tasks in Apple Notes.&lt;/p&gt;
&lt;div class="image"&gt;
&lt;img style="max-width: 462px" src="https://www.sglavoie.com/images/posts/0037_systematic_reviews/apple_reminders_daily_focus.png" alt="Apple Reminders: Daily focus lists" /&gt;
&lt;/div&gt;
&lt;p&gt;So in this case, everything being scheduled for today or that has been flagged manually will appear in the &lt;strong&gt;Today&lt;/strong&gt; list. Anything else that has a deadline in the near future will appear in the &lt;strong&gt;Scheduled&lt;/strong&gt; list. Then, I simply divide my set of &lt;em&gt;work in progress&lt;/em&gt; (&lt;strong&gt;WIP&lt;/strong&gt;) tasks into work and personal and will drag and drop tasks from other lists/projects as needed to re-prioritize them. To avoid having a large list of items in the &lt;strong&gt;WIP&lt;/strong&gt; lists, I use two separate lists to keep track of the &lt;strong&gt;Next&lt;/strong&gt; actions: these are tasks that do not require immediate attention but used to be scheduled as being more urgent, since priorities will often need to shift under a set of constantly new circumstances.&lt;/p&gt;
&lt;p&gt;These tasks will be dragged out of other lists as needed &amp;ndash; either from the backlog of tasks or from the &lt;strong&gt;WIP&lt;/strong&gt; tasks to declutter other lists. They will be prioritized as the &lt;strong&gt;Next&lt;/strong&gt; actions to ensure that once the &lt;strong&gt;WIP&lt;/strong&gt; is done, there is a continuity to the system. Just as with other lists, order matters, so that the most important tasks are at the top and the least important ones are at the bottom. This is a simple way to ensure that the most important tasks are always visible and that the least important ones are not forgotten.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="weekly-review"&gt;Weekly review&lt;/h2&gt;
&lt;p&gt;The weekly review is a bit more involved, but it is still quite simple. The main idea is to take a step back and reflect on the past week, while planning for the next one. This is where I like to take a look at my calendar and make sure that I have not forgotten anything important. Given the current set of tools I use, I don&amp;rsquo;t really implement &lt;a href="https://www.mindtools.com/a9bt6jr/timeboxing"&gt;timeboxing&lt;/a&gt; as I find that I just tend to miss stuff I put into the calendar if it&amp;rsquo;s not an &amp;ldquo;all-day&amp;rdquo; event. Brace yourself, here it goes.&lt;/p&gt;
&lt;div class="image"&gt;
&lt;img src="https://www.sglavoie.com/images/posts/0037_systematic_reviews/weeklyReview.svg" alt="Flow for systematic weekly reviews" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://www.sglavoie.com/files/posts/0037_systematic_reviews/systematic_reviews_weekly.plantuml"&gt;PlantUML source of this diagram&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This probably looks more intimidating than it really is. I usually go through the whole process in under an hour. That may sound like a lot, but it really helps to declutter my mind while giving me the assurance I did my best to plan for the upcoming week. I also like to do this on Sunday mornings, so that I can start the week with clear focus. All in all, I find that this is a great investment and it becomes especially true if I adhere to doing it in the mornings right after I have a delicious cup of freshly ground coffee (but at least 90 to 120 minutes after waking up: again, see Huberman to get fantastic explanations on why this is ideal).&lt;/p&gt;
&lt;h2 id="automatic-reviews"&gt;Automatic reviews&lt;/h2&gt;
&lt;p&gt;Once more, nothing too fancy is happening here, but having &lt;em&gt;smart lists&lt;/em&gt; makes it very easy to review what is coming up. In this case, it&amp;rsquo;s mostly empty as it excludes all the work in progress, which makes it more useful as well since these are already accessible from their own lists, so the review section really highlights anything that is salient and non-recurring.&lt;/p&gt;
&lt;div class="image"&gt;
&lt;img style="max-width: 538px" src="https://www.sglavoie.com/images/posts/0037_systematic_reviews/apple_reminders_reviews.png" alt="Tags and smart lists from Apple Reminders to review the week" /&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="monthly-and-yearly-reviews"&gt;Monthly and yearly reviews&lt;/h2&gt;
&lt;p&gt;I can see the benefit in asking more questions and taking more time to plan ahead up to a point, but life is totally unpredictable and I find that it is not worth spending too much time planning for the future. Ultimately, everything is ephemeral: interests, hobbies or even relationships come and go. So my current approach is simply to repeat the weekly process with an emphasis on the things I might have neglected a bit more, such as planning new projects, dissolving old ones that no longer require attention and making sure that I am still on track with my goals at a higher level.&lt;/p&gt;
&lt;p&gt;These moments also form a great opportunity to look at finances and make sure that I am not spending too much money on things that do not bring me joy or that I do not need. They also serve as a reminder to take a step back and reflect on the bigger picture, taking a break to look at all the progress that has been made so far. This becomes easier when things are tracked down by writing, and so I maintain a private GitHub repository called &lt;code&gt;life-trail&lt;/code&gt; in which I write down a summary of my weekly reviews, keep track of the most important lessons I have learned and main events that happened in my life. This is heavily inspired from having gone through the &lt;a href="https://www.selfauthoring.com/"&gt;Self Authoring Program&lt;/a&gt;, which I highly recommend to anyone interested in bringing more meaning into their life.&lt;/p&gt;
&lt;p&gt;While (I think) I&amp;rsquo;d love to know for sure where I spend every minute of the day, I tend to track my time with &lt;a href="https://toggl.com/"&gt;Toggl&lt;/a&gt; only when I&amp;rsquo;m at the computer or reading books on my pretty old Nook e-ink reader. It doesn&amp;rsquo;t paint the full picture, yet it is not cumbersome to use and provides the kinds of insights I am looking for while forcing me to be more mindful about the actions I take moment to moment.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I have tried many different methodologies to get more organized and, after getting no further than where I used to be, I have reached the following conclusion, which can be elegantly summed up with Nike&amp;rsquo;s tagline: &lt;em&gt;Just Do It&lt;/em&gt;. While one might have lofty goals, it all means nothing if one does not take action towards attaining them. This is why I have decided to adopt a more systematic approach in all aspects of life. This is not to say that I will not be using any tools, but rather that I will be using them with a focus on pragmatism and manifesting actual results.&lt;/p&gt;
&lt;h2 id="more-resources-and-references"&gt;More resources and references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hubermanlab.com/"&gt;Huberman Lab podcast&lt;/a&gt; - Andrew Huberman.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sglavoie.com/posts/building-task-planner-react-native/"&gt;ProductiviDo&lt;/a&gt; - GitHub.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.selfauthoring.com/"&gt;Self Authoring Program&lt;/a&gt; - Jordan Peterson.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/18079767-solving-the-procrastination-puzzle"&gt;Solving the Procrastination Puzzle: A Concise Guide to Strategies for Change&lt;/a&gt; - Timothy A. Pychyl.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://todoist.com/productivity-methods/weekly-review"&gt;The Weekly Review&lt;/a&gt; - Todoist.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mindtools.com/a9bt6jr/timeboxing"&gt;Timeboxing&lt;/a&gt; - MindTools.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://toggl.com/"&gt;Toggl&lt;/a&gt; - Toggl Time tracking.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.readworknotes.com/"&gt;Work Notes by Sam Matla&lt;/a&gt; - Sam Matla.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sun, 14 May 2023 15:58:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/systematic-reviews/</guid><category>workflow</category><category>processes</category><category>planning</category><category>software</category></item><item><title>Git the gist of it: common commands for a working workflow</title><link>https://www.sglavoie.com/posts/git-the-gist-of-it-common-commands-for-a-working-workflow/</link><description>Git is such a fascinating piece of software. It is not the only distributed version control system out there, but it is certainly ubiquitous in that category and has become a tool that must be mastered by any software developer in the modern era.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Git, the friend you wish you had met earlier, when organizing files like &lt;code&gt;report 1.pdf&lt;/code&gt;, &lt;code&gt;report 2.pdf&lt;/code&gt; and &lt;code&gt;report final 1.pdf&lt;/code&gt; still made sense somehow. It&amp;rsquo;s undoubtedly complicated to wrap one&amp;rsquo;s head around it at first, although there exist some GUIs to make the journey smoother – the best examples that come to mind would include &lt;a href="https://www.gitkraken.com/"&gt;GitKraken&lt;/a&gt; and &lt;a href="https://www.git-tower.com/"&gt;Tower&lt;/a&gt; at the top of the list. I have been advised to learn to use Git from the terminal and I would, without flinching, recommend anyone to do the same to really grok this tool and get an intuitive feel for what it does.&lt;/p&gt;
&lt;p&gt;Beyond the extraordinary technical prowess that it is, Git can be useful to a broad audience. It&amp;rsquo;s not good at dealing with large binary files (and &lt;a href="https://git-lfs.github.com/"&gt;Git LFS&lt;/a&gt; is not exactly easy to use just yet to cover that case), but for anything involving text that a human can read, it shines – may it be a programming language, some reports written with &lt;a href="https://www.latex-project.org/"&gt;LaTeX&lt;/a&gt;, some diagrams produced with &lt;a href="http://mermaid-js.github.io/mermaid/#/"&gt;Mermaid&lt;/a&gt; or &lt;a href="https://plantuml.com/"&gt;PlantUML&lt;/a&gt;, personal notes taken in plain text or in Markdown or the fact that synchronizing changes made to a project is a breeze and allows ones to work from multiple locations and machines.&lt;/p&gt;
&lt;p&gt;With platforms like &lt;a href="https://github.com/"&gt;GitHub&lt;/a&gt; and &lt;a href="https://about.gitlab.com/"&gt;GitLab&lt;/a&gt;, it is easy to keep a &amp;ldquo;backup&amp;rdquo; of projects, which can be either public or private. And despite Git not being awesome with very large files, any smaller binary files like images and PDF files can easily be added to a Git repository, allowing users to keep different versions of these files very easily without using a cloud service such as Dropbox or Google Drive. So even if the original input is not written in a text-friendly manner, the output can still be saved under multiple versions with great details, backed up to different websites and fetched just as conveniently from anywhere else (assuming access has been set up to continue working on a project, or at least one can log in to the website and download files without issue).&lt;/p&gt;
&lt;p&gt;Sure, it is not for everyone, but the use cases just described can go a long way. I have used it to practice solving programming challenges, to document all my project at the university, to keep track of preferences and configuration files for my different systems (Linux and macOS) which I&amp;rsquo;ve described in &lt;a href="https://www.sglavoie.com/posts/managing-dotfiles-with-git-bare-repository/"&gt;Managing dotfiles with a Git bare repository&lt;/a&gt;, to work on pet programming projects and coding experiments that I keep private, to store code snippets into a growing library for reference purposes, to take notes in the form of &amp;ldquo;tutorials&amp;rdquo; or &amp;ldquo;wiki&amp;rdquo; (platforms like GitHub and GitLab have a nice wiki feature that renders prose nicely!) for different tasks I need to do infrequently and of course to build this very website in the open (the source code is &lt;a href="https://github.com/sglavoie/sglavoie.github.io-source"&gt;available on GitHub&lt;/a&gt;)! And that is just describing some of my personal use cases without touching on the fact that collaboration within a team is a huge reason to use it!&lt;/p&gt;
&lt;p&gt;So with that said, let&amp;rsquo;s explore some of its functionality, starting with a few tips and tricks, then delving deeper into a real-world workflow and topping it off with more useful commands and configuration settings.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="global-and-local-gitignore"&gt;Global and local &lt;code&gt;.gitignore&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;There are files we never care about adding to Git repositories, such as a thumbnail cache file &lt;code&gt;Thumbs.db&lt;/code&gt; on Windows or a &lt;code&gt;.DS_Store&lt;/code&gt; file storing custom attributes for folders on macOS. Instead of ignoring these kinds of entries in a per-repository &lt;code&gt;.gitignore&lt;/code&gt; file which might not contain everything we would like to exclude, it can be simpler and more efficient to use a global &lt;code&gt;.gitignore&lt;/code&gt;, which serves the same purpose but for &lt;em&gt;any&lt;/em&gt; Git repository regardless of the presence of a &lt;code&gt;.gitignore&lt;/code&gt; in that specific repository! In other words, this serves as a permanent list of things to ignore so that there is no need to remember about them later.&lt;/p&gt;
&lt;p&gt;The setup is straightforward: create a file &lt;code&gt;~/.gitignore&lt;/code&gt;, fill it as usual with patterns you are sure to want to exclude globally and set a configuration option to use that file – for instance by adding the following to &lt;code&gt;.gitconfig&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;git&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;pre tabindex="0"&gt;&lt;code class="language-git" data-lang="git"&gt;[core]
 excludesfile = ~/.gitignore&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://www.toptal.com/"&gt;Toptal&lt;/a&gt; provides &lt;a href="https://docs.gitignore.io/install/command-line"&gt;a nice tool on the command-line&lt;/a&gt; that can be used to easily exclude patterns. With it, typing &lt;code&gt;gi python &amp;gt;&amp;gt; .gitignore&lt;/code&gt; would append a bunch of common patterns to a &lt;code&gt;.gitignore&lt;/code&gt; file – in this case, for Python.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="meaningful-commit-messages-with-gitmessage"&gt;Meaningful commit messages with &lt;code&gt;.gitmessage&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Besides using the excellent &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/"&gt;Conventional Commits&lt;/a&gt; specification to write messages in a format that makes sense to other fellow human beings, using a template for Git commits can help with remembering &lt;em&gt;why&lt;/em&gt; commits are made and how they should be structured. There is a configuration setting, &lt;code&gt;commit.template&lt;/code&gt;, that can be used to set the default text being displayed when a text editor opens after typing &lt;code&gt;git commit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following is the current template I use, which can be saved in a file like &lt;code&gt;~/.gitmessage&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# [Add/Fix/Remove/Update/Refactor/Document] [summary]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Why is it necessary? (Bug fix, feature, improvements?)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# How does the change address the issue?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# What side effects does this change have?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Include a link to the ticket, if any.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then, this can be added to the config file under the &lt;code&gt;[commit]&lt;/code&gt; section like so:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;git&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;pre tabindex="0"&gt;&lt;code class="language-git" data-lang="git"&gt;[commit]
 template = ~/.gitmessage&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;All lines starting with a hash symbol (&lt;code&gt;#&lt;/code&gt;) will be ignored, so there is no need to manually delete all of this verbose content when saving the commit message. Sweet!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="my-usual-git-workflow"&gt;My usual Git workflow&lt;/h2&gt;
&lt;p&gt;This section depends very much on team standards, if applicable. But in general terms and considering mostly a solo interaction with Git, I like to adhere to the following processes, without taking into account how one would plan, produce diagrams, brainstorm, etc. That shall be the topic of an upcoming post!&lt;/p&gt;
&lt;h2 id="starting-a-new-project"&gt;Starting a new project&lt;/h2&gt;
&lt;p&gt;There are different ways to proceed, although I like the simplicity of just creating a new empty repository on GitHub and cloning it on my machine using the SSH protocol, e.g. &lt;code&gt;git clone git@github.com:some/project.git&lt;/code&gt;. This has the benefit of automatically setting the &lt;code&gt;main&lt;/code&gt; branch and the remote URL, using &lt;code&gt;origin&lt;/code&gt; as the default remote. Cloning an empty repository has the same effect as starting with &lt;code&gt;git init&lt;/code&gt;, so this also becomes unnecessary.&lt;/p&gt;
&lt;p&gt;If I have been experimenting locally first, I would do a &lt;code&gt;git init&lt;/code&gt;, commit any changes and then push the new repository on GitHub, either using the great Git integration provided by VS Code or again creating a new repository on GitHub to set the remote URL in order to be able to push local changes, i.e. by doing &lt;code&gt;git remote add origin git@github.com:some/project.git&lt;/code&gt; and then pushing to that new URL. To make sure that my default branch is always &lt;code&gt;main&lt;/code&gt; (to match the new default on GitHub) and not something like &lt;code&gt;dev&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt;, I have a Git template lying around that ensures that the first branch created when doing &lt;code&gt;git init&lt;/code&gt; will be &lt;code&gt;main&lt;/code&gt;. It is as simple as creating the file &lt;code&gt;~/.git-templates/HEAD&lt;/code&gt; with this content:&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ref: refs/heads/main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then, in &lt;code&gt;~/.gitconfig&lt;/code&gt;, the following section is used to read the templates:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;git&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;pre tabindex="0"&gt;&lt;code class="language-git" data-lang="git"&gt;[init]
 defaultBranch = main
 templateDir = ~/.git-templates&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I have this template since before &lt;code&gt;init.defaultBranch&lt;/code&gt; was introduced in Git 2.28.0 back in July 2020: now just having the option &lt;code&gt;defaultBranch&lt;/code&gt; will do the trick. Still, it&amp;rsquo;s good to have a &lt;code&gt;templateDir&lt;/code&gt; configured for other purposes! Although admittedly, I don&amp;rsquo;t use them at all and would rather create a custom command to more easily script what I need for each repository: more on this in the section &lt;a href="#custom-git-sub-commands"&gt;Custom Git sub-commands&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="committing-to-an-existing-project"&gt;Committing to an existing project&lt;/h2&gt;
&lt;p&gt;Now that there is some folder set up for Git to track files, it&amp;rsquo;s time to make changes! I&amp;rsquo;m a big fan of committing &amp;ldquo;small and often&amp;rdquo; because Git is super flexible and updating the history is usually a simple process – unless you have pushed to a remote repository used by other people, in which case being a &amp;ldquo;force push&amp;rdquo; type of person won&amp;rsquo;t make you many friends. May the &lt;code&gt;--force&lt;/code&gt; be with you when you push to a private repository where you&amp;rsquo;re the only contributor, otherwise, it&amp;rsquo;s good practice to think twice before sending your final changes away as you should not mess up with public history (and depending on how your access is set up within a team, you might not even be able to use the &lt;code&gt;--force&lt;/code&gt; flag when pushing on certain branches!).&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s the alternative to constantly rewriting history in a harmful way? Well, assuming you have a choice when working alone for instance, there are two main contenders: &lt;em&gt;merge&lt;/em&gt; vs &lt;em&gt;rebase&lt;/em&gt;. I think neither option is better than the other when used right, but it&amp;rsquo;s good to know that &lt;em&gt;rebase&lt;/em&gt; might create possible headaches because it modifies the Git history while &lt;em&gt;merge&lt;/em&gt; might create undesirable noise when used profusely by adding &amp;ldquo;merge commits&amp;rdquo;. I use both techniques, but for different purposes.&lt;/p&gt;
&lt;h3 id="being-committed"&gt;Being committed&lt;/h3&gt;
&lt;p&gt;The basic workflow might look like this, with a couple of different options thrown in to cover more scenarios:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Check the current state of the repo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Inspect the changes to commit (before staging)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Stage a file to be committed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add SOME_FILE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Stage all new/modified files, (i.e., --all)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add -A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Stage all modified files (not any new ones, i.e., --update)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add -u
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Get fancy with adding patches only (part of files)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add -p
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Undo changes done to a tracked file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git restore -- FILE_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Inspect staged changes to commit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff --staged
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Unstage files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git reset HEAD --
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# or just &amp;#39;git commit&amp;#39; to open a text editor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;SOME_MESSAGE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# See the changes done in the latest commit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git show
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# or a specific commit hash from earlier&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git show COMMIT_HASH &lt;span class="c1"&gt;# or ref like HEAD^&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Forgot to add something?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add SOME_OTHER_FILE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit --amend --no-edit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Want to rephrase the last commit?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit --amend
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Tag the latest commit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git tag TAG_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Tag earlier commit with annotation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git tag -a TAG_NAME COMMIT_HASH -m &lt;span class="s2"&gt;&amp;#34;MESSAGE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Undo a commit, keeping changes in the work tree&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git reset HEAD^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Undo a commit and DO NOT keep changes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git reset --hard HEAD^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Oops, get back a commit that was &amp;#34;lost&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git reflog &lt;span class="c1"&gt;# find the relevant commit hash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git cherry-pick COMMIT_HASH &lt;span class="c1"&gt;# or git checkout COMMIT_HASH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Explore the repo at an earlier commit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout COMMIT_HASH
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Committed some changes while checking out an old commit?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Save the changes to a new branch, then potentially merge them back&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git switch -c NEW_BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git switch OTHER_BRANCH
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge NEW_BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Undo a merge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git reflog &lt;span class="c1"&gt;# find the commit hash before the merge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git reset --hard COMMIT_HASH
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Or undo to a known point from a remote branch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git reset --hard origin/main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Stop tracking a file/directory (remove) but keep it on disk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rm --cached FILE_OR_DIRECTORY
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Remove stuff that hasn&amp;#39;t been committed yet (interactive mode is nice)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Don&amp;#39;t forget about -n for a dry run first!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clean -i
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Want to undo a change that&amp;#39;s already pushed?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Add a new commit without rewriting history&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git revert COMMIT_HASH
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# See who did what to which file and when&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git blame FILE_NAME&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Arguably, basic editing doesn&amp;rsquo;t require stashing, but that can be helpful for a quick modification, like temporarily saving changes to quickly switch to another branch or pull some remote changes without having to commit first.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# git stash --help - &amp;#34;Stash the changes in a dirty working directory away&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Maybe you want to keep changes around without committing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# --include-untracked and --keep-index might be needed!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Stash changes with a clearer name/purpose&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash save &lt;span class="s2"&gt;&amp;#34;SOME MESSAGE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Selectively stash changes (keep specific lines)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash save -p
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# List existing stashes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# See the summary of a stash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash show
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# See all changes made in a stash (patch)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash show -p
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Apply changes from a stash (TAB completion after `apply` is useful)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash apply stash@&lt;span class="se"&gt;\{&lt;/span&gt;SOME_ID&lt;span class="se"&gt;\}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Apply and remove the last stash at the same time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash pop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Remove a single stash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash drop stash@&lt;span class="se"&gt;\{&lt;/span&gt;SOME_ID&lt;span class="se"&gt;\}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Remove all stashes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash clear&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="rebase"&gt;&lt;code&gt;rebase&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When working locally with changes that are not yet part of the &amp;ldquo;permanent&amp;rdquo; Git history, I like to &lt;code&gt;git rebase&lt;/code&gt; very much to clean things up before making a &lt;code&gt;git push&lt;/code&gt;, which I like to view as an irrevocable decision. Because I &lt;em&gt;commit small and often&lt;/em&gt;, it&amp;rsquo;s often the case that I end up with a couple of very simple commits which really belong together and should form a bigger commit. While committing a big chunk of code at once avoids having to do any &lt;code&gt;rebase&lt;/code&gt; in the first place, it&amp;rsquo;s also inconvenient to undo a substantial amount of work when you&amp;rsquo;re experimenting and/or working on an unfamiliar codebase. So in the end, my preference goes towards smaller commits because rebasing is actually fun to do and lets you believe that you came up with brilliant solutions on your first attempt :). That makes for a cleaner Git history for sure, although you will lose some context if you never &lt;code&gt;merge&lt;/code&gt;. Meaning, rebasing creates a &amp;ldquo;linear&amp;rdquo; history while merging from different branches shows a more complete picture of how a repository really took shape over time. My simple rule of thumb on the matter is this: use &lt;code&gt;rebase&lt;/code&gt; to consistently ship meaningful commits (good descriptions, fewer typos, changes chunked logically and precisely, etc.) and use &lt;code&gt;merge&lt;/code&gt; to incorporate somewhat larger pieces of history, like a new feature coming from a &amp;ldquo;feature branch&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Some common commands I use:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# git rebase --help - &amp;#34;Reapply commits on top of another base tip&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Include all commits up to COMMIT_HASH, included&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase -i COMMIT_HASH^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Include most recent commit only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase -i HEAD^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Include 2 most recent commits&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase -i HEAD^^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Include 3 most recent commits&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase -i HEAD~3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# make BRANCH have the history up to COMMIT_OR_BRANCH, included&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase COMMIT_OR_BRANCH BRANCH
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Used after resolving a conflict&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase --continue
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Reset to where you were before rebasing (move HEAD)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase --abort
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# To update the next actions to be taken on the remaining commits&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase --edit-todo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;There are a bunch of obscure options just like almost any other Git command in existence, but for that there is help available for each command (such as &lt;code&gt;git rebase --help&lt;/code&gt;) from the terminal and the trusty Stack Overflow ;).&lt;/p&gt;
&lt;h4 id="sample-workflow"&gt;Sample workflow&lt;/h4&gt;
&lt;p&gt;In the case of &lt;code&gt;rebase&lt;/code&gt;, I enjoy working in this way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make small commits, maybe 5 to 10 depending on the complexity of the project – usually as long as a series of commits relates to a single topic whenever possible.&lt;/li&gt;
&lt;li&gt;Have a peak at the recent history with &lt;code&gt;git log&lt;/code&gt; to have a good idea of what just happened.&lt;/li&gt;
&lt;li&gt;Realize that the history can be improved. Find the oldest commit hash (not pushed yet!), take note of it and run &lt;code&gt;git rebase -i COMMIT_HASH^&lt;/code&gt; (with the &amp;ldquo;hat&amp;rdquo; &lt;code&gt;^&lt;/code&gt; at the end to reference the parent of that commit).&lt;/li&gt;
&lt;li&gt;This opens up a text editor with some explanations on how to proceed (for more on this, have a look at this great article on &lt;a href="https://www.atlassian.com/git/tutorials/rewriting-history"&gt;Rewriting history&lt;/a&gt;). From there, I usually use &lt;code&gt;r&lt;/code&gt; to reword, &lt;code&gt;s&lt;/code&gt; to squash, &lt;code&gt;e&lt;/code&gt; to edit and sometimes &lt;code&gt;f&lt;/code&gt; to fixup, besides also re-ordering commits and from time to time deleting one by removing the line entirely.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt; is pretty harmless as it will just show up a new window where the commit message and description can be modified.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; is more destructive because commits will be &amp;ldquo;merged&amp;rdquo; together! No actual change is lost, but if you squash a ton of commits by accident, it will create huge commits and you might want to undo that with &lt;code&gt;reflog&lt;/code&gt; before it&amp;rsquo;s too late (Atlassian has more to say about this in &lt;a href="https://www.atlassian.com/git/tutorials/rewriting-history/git-reflog"&gt;git reflog&lt;/a&gt;). The &lt;code&gt;s&lt;/code&gt; option is also nice because it keeps all commit messages, so you can edit the final log message as needed without discarding messages.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;e&lt;/code&gt; is used to modify the actual source code being committed, maybe to fix a typo or a minor bug after realizing that the test suite no longer passes (assuming you are not programming blindly without tests :)). This can cause some &amp;ldquo;conflicts&amp;rdquo; if you change some lines which are also part of other commits in the selection you made originally, so one has to be careful with that. This can also be used in conjunction with &lt;code&gt;git reset HEAD^&lt;/code&gt; to &amp;ldquo;cancel&amp;rdquo; the current commit when doing the rebase in order to split it into multiple smaller commits, which can then be &amp;ldquo;&lt;code&gt;git add&lt;/code&gt;ed&amp;rdquo; back, followed by a &lt;code&gt;git rebase --continue&lt;/code&gt; to keep going.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;f&lt;/code&gt; is a quick way of squashing commits together while discarding messages you don&amp;rsquo;t need. So maybe the last commit has a proper description (&amp;ldquo;Implemented something&amp;hellip;&amp;rdquo;) and a few commits before that one were just temporary &amp;ldquo;savepoints&amp;rdquo; with non-sensical commit messages (things like &amp;ldquo;testing&amp;hellip;&amp;rdquo; or &amp;ldquo;new commit&amp;rdquo;) that were done until you reached a final solution that&amp;rsquo;s worth committing. So of course in that case the history would be pretty ugly and hard to understand if all commits were kept intact, so these can be added to the latest change which encompasses a working solution as a whole. Just as well, maybe that last commit didn&amp;rsquo;t have a complete message just yet, so it could also be rewritten with the &lt;code&gt;r&lt;/code&gt; prefix.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;When individual commits make the &lt;code&gt;rebase&lt;/code&gt; stop temporarily (such as when a commit is to be edited with &lt;code&gt;e&lt;/code&gt; and waits for changes to be made), then it&amp;rsquo;s time to do &lt;code&gt;git rebase --continue&lt;/code&gt; once the necessary changes are made.&lt;/li&gt;
&lt;li&gt;If something goes wrong along the way, there&amp;rsquo;s always a way back to the place you were right before rebasing with &lt;code&gt;git rebase --abort&lt;/code&gt;. It&amp;rsquo;s worth noting that Git itself will output some useful messages all along so it&amp;rsquo;s not necessary to remember all of this: it quickly becomes intuitive.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, &lt;code&gt;rebase&lt;/code&gt; covers the cases where local changes are done in isolation, but for something more imposing or simply to keep track of what happened in which branch, &lt;code&gt;merge&lt;/code&gt; remains a powerful ally.&lt;/p&gt;
&lt;h3 id="merge"&gt;&lt;code&gt;merge&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;So I don&amp;rsquo;t use &lt;code&gt;merge&lt;/code&gt; as much, especially when working solo, but it is undeniably useful and could replace the &lt;code&gt;rebase&lt;/code&gt; workflow completely (it might also be demanded by your colleagues anyways!). There&amp;rsquo;s nothing like a good &lt;code&gt;rebase&lt;/code&gt; session to keep things tidy, but a flat history line won&amp;rsquo;t look attractive on a huge project with many contributors, so &lt;code&gt;merge&lt;/code&gt; is there to deal with cleanly integrating changes from different places into some &lt;code&gt;main&lt;/code&gt; branch (it could be named differently based on the team workflow of course and there might be more than one &amp;ldquo;main&amp;rdquo; branch too). What makes &lt;code&gt;merge&lt;/code&gt; incredibly cool is the fact that branches in Git are &amp;ldquo;cheap&amp;rdquo;, meaning they can be created very quickly and efficiently, they don&amp;rsquo;t take space at all, you could have dozens of branches or more and you can incorporate changes from one branch to another with a simple &lt;code&gt;git merge&lt;/code&gt;, which is nicer than using &lt;code&gt;git cherry-pick&lt;/code&gt; to get multiple commits from one branch into another (for one or two commits though, &lt;code&gt;cherry-pick&lt;/code&gt; is handy).&lt;/p&gt;
&lt;p&gt;I see &lt;code&gt;rebase&lt;/code&gt; more like a cleaning step while &lt;code&gt;merge&lt;/code&gt; really shines when dealing with multiple commits between different branches. Even on a simple project when working alone, it has its uses! For example, let&amp;rsquo;s say we have a repository full of small programming challenges (maybe from &lt;a href="https://www.hackerrank.com/"&gt;HackerRank&lt;/a&gt; or &lt;a href="https://leetcode.com/"&gt;LeetCode&lt;/a&gt;). We might get stuck on a tricky problem and wouldn&amp;rsquo;t want to commit an unfinished solution. Also, maybe we would get fed up eventually working on a given challenge and would rather try our luck with a different one. Well, we can always leave changes uncommitted (not very safe as these might be lost!), but that can cause confusion and you might inadvertently end up committing files that have nothing to do with the latest challenge – effectively committing at the same time your unfinished challenge with the one you just completed. There&amp;rsquo;s always the possibility to &lt;code&gt;git stash&lt;/code&gt; individual files to keep them somewhere, but I find working with stashes to be a bit more cumbersome than branches and you might literally forget that you have changes stashed. If you do that a few times, you might not even know which stash is what if you didn&amp;rsquo;t give them a good name&amp;hellip;&lt;/p&gt;
&lt;p&gt;Some common commands I use:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# git merge --help - &amp;#34;Join two or more development histories together&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Get commits from BRANCH_NAME into the currently checked one&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Merge one branch into another&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge SOURCE_BRANCH_NAME DESTINATION_BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Get out of conflicts...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge --abort
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Continue after resolving conflicts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge --continue
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Keep the &amp;#34;merge commit&amp;#34; in the history&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge --no-ff BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set a custom commit message for a &amp;#34;merge commit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge -m &lt;span class="s2"&gt;&amp;#34;MESSAGE&amp;#34;&lt;/span&gt; BRANCH&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;sub&gt;Tip: The command &lt;code&gt;git merge --help&lt;/code&gt; describes some nice examples about the whole process of conflict resolution.&lt;/sub&gt;&lt;/p&gt;
&lt;h4 id="sample-workflow-kinda"&gt;Sample workflow (kinda)&lt;/h4&gt;
&lt;p&gt;So, instead of risking losing changes like this, creating a new branch with &lt;code&gt;git switch -c BRANCH_NAME&lt;/code&gt; and working from there is much easier to confine changes to a specific matter, which we can see like a &amp;ldquo;feature&amp;rdquo; that can later be added to the main &amp;ldquo;trunk&amp;rdquo;. We can commit unfinished business, switch to a different branch, come back to it later, &lt;code&gt;git reset&lt;/code&gt; any old commit to &amp;ldquo;revert&amp;rdquo; the history and keep working from a clean state. In any case, there&amp;rsquo;s always &lt;code&gt;rebase&lt;/code&gt; to avoid committing useless stuff, so there&amp;rsquo;s really no reason to avoid branches.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;merge&lt;/code&gt; command will be involved when changes from a &amp;ldquo;feature branch&amp;rdquo; (or whatever other purpose you give a branch) are merged into the main branch with &lt;code&gt;git merge BRANCH_NAME&lt;/code&gt;. When there are no &amp;ldquo;merge conflicts&amp;rdquo; (such as when you work on a totally new file that won&amp;rsquo;t clash with prior work), this is a seamless workflow where you can just &lt;code&gt;merge&lt;/code&gt; as if changes were made in the branch you are on in the first place. There are other instances where bigger changes can be merged successfully in the same way, but it&amp;rsquo;s interesting to keep track of how you worked on a project, so in this scenario you can always use &lt;code&gt;git merge --no-ff BRANCH_NAME&lt;/code&gt; (&lt;code&gt;--no-ff&lt;/code&gt; meaning &amp;ldquo;&lt;em&gt;no fast-forward&lt;/em&gt;&amp;rdquo;) to keep a &amp;ldquo;&lt;em&gt;merge commit&lt;/em&gt;&amp;rdquo;, which is strictly speaking not needed (because there&amp;rsquo;s no conflict resolution in the case of a &amp;ldquo;successful&amp;rdquo; merge) but can be a helpful reference point when you look back at your Git history two years from now to understand how you worked on something, piecing things together bit by bit.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also an indisputable advantage conferred by the use of branches, which is: &lt;em&gt;you can have plenty of them&lt;/em&gt;! So while &lt;code&gt;rebase&lt;/code&gt; is nice to use on a given branch, in no way can working on multiple things at once in a single branch and then rebasing all that effort be as straightforward as creating new branches for every new topic that&amp;rsquo;s being started and then merging it back into a single place of truth. Juggling different concerns in a single branch where you have a few things going on (maybe 2-3 partially implemented features) is far from being a cozy environment and rebasing that kind of work where commits are not even related to each other can become a nightmare in a short amount of time.&lt;/p&gt;
&lt;h2 id="managing-branches"&gt;Managing branches&lt;/h2&gt;
&lt;p&gt;So with all that was written earlier, I believe a point was made in favour of using branches. Because branches are such a wonderful feature of Git, here are some more commands I rely on (surprisingly, there aren&amp;rsquo;t that many for &lt;code&gt;git branch&lt;/code&gt;!):&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# git branch --help - &amp;#34;List, create, or delete branches&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# List local branches&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# List only remote branches&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch -r
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# List all branches&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch -a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Delete a branch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch -d BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Delete a branch forcefully&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch -D BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Delete a remote branch (doesn&amp;#39;t matter if the local branch was deleted)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin --delete REMOTE_NAME/BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Rename a branch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch -m BRANCH_NAME NEW_BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Track a specific remote branch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch BRANCH_NAME -u UPSTREAM_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# See which branches have been merged or not&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch --merged
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch --no-merged
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Switch to the branch last checked out&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout -
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Compare changes between branches&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff BRANCH_ONE..BRANCH_TWO
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Compare a file across branches&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff BRANCH_ONE:FILE_NAME BRANCH_TWO:FILE_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# See a file from another branch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git show BRANCH_NAME:FILE_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Get a file from a different branch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git restore --source OTHER_BRANCH_NAME path/to/file.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="aliases"&gt;Aliases&lt;/h2&gt;
&lt;p&gt;While I believe it&amp;rsquo;s good practice to type complete commands to remember them better, I also tend to use Git aliases for two reasons: because some deeply ingrained concepts don&amp;rsquo;t need as much reinforcement and because some obscure operations can be made so much more accessible with a pertinent mnemonic device.&lt;/p&gt;
&lt;p&gt;Some commands like &lt;code&gt;add&lt;/code&gt; just become unforgettable over time, while others – e.g., &lt;code&gt;unstage&lt;/code&gt; – just don&amp;rsquo;t exist yet. Without further ado, here is a list of the aliases I currently rely on when using Git, which comes straight from &lt;code&gt;~/.gitconfig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For instance, instead of typing &lt;code&gt;git add&lt;/code&gt;, by setting the alias &lt;code&gt;a = add&lt;/code&gt; (see below), it is now possible to type &lt;code&gt;git a&lt;/code&gt; to get the same result.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;git&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;pre tabindex="0"&gt;&lt;code class="language-git" data-lang="git"&gt;[alias]
 a = add
 br = branch
 c = commit
 ca = commit --amend
 can = commit --amend --no-edit
 ch = checkout
 d = diff
 ds = diff --staged
 po = push origin
 pos = push --set-upstream origin main
 rc = rebase --continue
 ri = rebase -i
 rl = reflog
 sh = show
 s = status --short
 st = status
 sw = switch
 t = tag
 unstage = reset HEAD --

 ; show changes since last commit
 difflast = diff --cached HEAD^

 ; show last commit
 last = l -1 HEAD

 ; undo last commit
 undo = &amp;#34;!f() { git reset --hard $(git rev-parse \
--abbrev-ref HEAD)@{${1-1}}; }; f&amp;#34;

 l = log

 ; short log
 sl = shortlog

 ; log graph
 lg = l --color --graph --pretty=format:&amp;#39;%Cred%h%Creset \
-%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)&amp;lt;%an&amp;gt;%Creset&amp;#39; --abbrev-commit

 ; log name
 ln = l --name-only

 ; log decorate all
 logda = l --oneline --decorate --graph --all

 ; log stat
 ls = l --stat

 ; log pretty
 lp = log --pretty=&amp;#39;%C(yellow)%h %C(cyan)%ad %Cblue%aN%C(auto)%d \
%Creset%s&amp;#39; --date=relative --date-order --graph&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;At some point, using Git aliases will become second nature, but until then, it can be useful to have a separate terminal alias to list them all (or maybe you forgot the command an alias is using under the hood!). For this, you can set the following alias in a file like &lt;code&gt;~/.bash_aliases&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;gitaliases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;git config -l | grep alias | sed &amp;#34;s/^alias\.//g&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# For a fancier version displaying aliases in two columns neatly aligned:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;gitaliases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;git config -l | grep alias | sed &amp;#34;s/^alias\.//g&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; | sed &amp;#34;s/=/Ω/&amp;#34; | column -t -s &amp;#34;Ω&amp;#34;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;That command will look in the Git configuration, extract all the lines containing &amp;ldquo;alias&amp;rdquo; and remove the &amp;ldquo;&lt;code&gt;alias.&lt;/code&gt;&amp;rdquo; prefix so that you can see all aliases in the form &lt;code&gt;a=add&lt;/code&gt; in the case of &lt;code&gt;git add&lt;/code&gt; being aliased to &lt;code&gt;git a&lt;/code&gt;. If you have a ton of aliases (which is probably a red flag&amp;hellip;), finding a specific one is just a matter of doing &lt;code&gt;gitaliases | grep keyword&lt;/code&gt;, of course replacing &lt;code&gt;keyword&lt;/code&gt; with something else ;).&lt;/p&gt;
&lt;p&gt;&lt;sub&gt;Tip: One can go further to shorten Git commands by shortening &lt;code&gt;git&lt;/code&gt; itself! You can put a line such as &lt;code&gt;alias g='git'&lt;/code&gt; in &lt;code&gt;~/.bash_aliases&lt;/code&gt; for instance. Now, &lt;code&gt;git add&lt;/code&gt; can become &lt;code&gt;g a&lt;/code&gt;. Pretty efficient! For more on Bash aliases, you might like to read &lt;a href="https://www.sglavoie.com/posts/aliases-also-known-as-terminal-users-best-friends/"&gt;Aliases: Also Known as Terminal User’s Best Friends&lt;/a&gt;.&lt;/sub&gt;&lt;/p&gt;
&lt;p&gt;This section is really just meant to give a taste of the possibilities that aliases offer. To learn about actual commands and their usage, there is nothing like the &lt;a href="https://git-scm.com/docs"&gt;official Git docs&lt;/a&gt; (there is a &lt;a href="https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases"&gt;short section on Git aliases&lt;/a&gt; too).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="custom-git-sub-commands"&gt;Custom Git sub-commands&lt;/h2&gt;
&lt;p&gt;There might be less of a need for custom-made commands to be created when so much can be done with Git alone, but there are cases where these custom additions can shine! As a concrete example, I have been updating this website with what I call my &amp;ldquo;&lt;a href="https://www.sglavoie.com/posts/what-it-took-to-propel-a-career-in-tech-in-five-years/"&gt;learning progress&lt;/a&gt;&amp;rdquo; but committing always the same kind of stuff gets boring, so I have created a simple Bash script to automate the process of publishing and committing (the content of the script is of no importance, it is only there to demonstrate that commands other than Git can be called too):&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Act as custom command `git addlearning` and perform all that follows&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Assumes that `pelican` command is available&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/dev/sglavoie/sglavoie.github.io-source
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Add learning progress&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pelican
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp -r &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ~/dev/sglavoie/sglavoie.github.io-source/output/* &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ~/dev/sglavoie/sglavoie.github.io/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/dev/sglavoie/sglavoie.github.io &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git add .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Add learning progress&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/dev/sglavoie/sglavoie.github.io-source&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It is noteworthy to know that any kind of executable programs can be used, such as Ruby, Python and so on, as long as they are made available in the &lt;code&gt;$PATH&lt;/code&gt;. This will be somewhat similar on most UNIX-based system where the &lt;code&gt;PATH&lt;/code&gt; variable needs to be exported (usually in a file like &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt;), for instance:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# $HOME refers to a place like /home/user on Linux or /Users/user on macOS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/dev/git-scripts:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;On Windows, there are &lt;a href="https://superuser.com/a/143121/1032549"&gt;separate instructions to be followed&lt;/a&gt;. With that done, there&amp;rsquo;s also a need to make sure the file is executable, which can be done on the command-line with &lt;code&gt;chmod +x FILENAME_HERE&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="some-lesser-known-yet-incredibly-useful-commands"&gt;Some lesser-known yet incredibly useful commands&lt;/h2&gt;
&lt;h2 id="git-bisect"&gt;&lt;code&gt;git bisect&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;I try to commit working code as often as possible, but sometimes there are just bugs that have flown by and took residence in the codebase a long time ago. In this scenario, there is one tool in the Git tool belt for that very specific use case: &lt;code&gt;git bisect&lt;/code&gt;. Working with the &lt;a href="https://en.wikipedia.org/wiki/Binary_search_algorithm"&gt;binary search algorithm&lt;/a&gt;, &lt;code&gt;git bisect&lt;/code&gt; searches in a range of commits and by identifying &amp;ldquo;good&amp;rdquo; and &amp;ldquo;bad&amp;rdquo; commits, will pinpoint exactly where the issue came from in the minimum number of iterations possible. You&amp;rsquo;ll need sub-commands like &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;bad&lt;/code&gt;, &lt;code&gt;good&lt;/code&gt; and &lt;code&gt;reset&lt;/code&gt; at the very least and for more advanced use cases where automatically running a script for each commit is needed, &lt;code&gt;run&lt;/code&gt; will do the job. See &lt;a href="https://git-scm.com/docs/git-bisect"&gt;the docs on &lt;code&gt;git bisect&lt;/code&gt;&lt;/a&gt; for more.&lt;/p&gt;
&lt;h2 id="git-rerere"&gt;&lt;code&gt;git rerere&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;I almost never use this in practice, but it &lt;em&gt;can&lt;/em&gt; be very useful when working in a large project where similar merge conflicts are likely to occur over time. Git can be &amp;ldquo;taught&amp;rdquo; to automatically solve merge conflicts! There are some &lt;a href="https://git-scm.com/docs/git-rerere"&gt;official docs on this&lt;/a&gt;, but I&amp;rsquo;ve found this &lt;a href="https://stackoverflow.com/a/49501436/8787680"&gt;Stack Overflow answer&lt;/a&gt; to be a more approachable read.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="a-word-on-the-configuration-file"&gt;A word on the configuration file&lt;/h2&gt;
&lt;p&gt;At the very minimum, setting a name and email will be necessary to use Git:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config --global user.name &lt;span class="s2"&gt;&amp;#34;Your Name&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config --global user.email &lt;span class="s2"&gt;&amp;#34;email@example.com&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Seeing the current configuration can come in handy, too:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config --global --list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And in the case of this particular command, &lt;code&gt;git config&lt;/code&gt;, it can&amp;rsquo;t be emphasized enough how useful searching through the help page in the terminal will be: &lt;code&gt;git config --help&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d be remiss if I didn&amp;rsquo;t include an example of my own configuration at this point, so here it goes! To make it so that it is convenient to commit this configuration file publicly, some private settings can be &amp;ldquo;included&amp;rdquo; from other paths inside the &lt;code&gt;[include]&lt;/code&gt; block. For clarity, I&amp;rsquo;ll show what that file looks like below the main configuration file.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# This is the content of ~/.gitconfig&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;include&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;;&lt;/span&gt; This file can add more information to extend the main configuration.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;;&lt;/span&gt; For example, when inside a directory that match a certain pattern,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;;&lt;/span&gt; we can tell Git to change our name or email &lt;span class="o"&gt;(&lt;/span&gt;e.g., when committing
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;;&lt;/span&gt; with different credentials from a work email&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; ~/Dropbox/.custom/.gitconfig
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;color&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; auto
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; auto
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;grep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; auto
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;interactive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; auto
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; auto
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;alias&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# cut out for brevity, see the section on Aliases&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;core&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; nvim
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;excludesfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; ~/.gitignore
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;pager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; diff-so-fancy &lt;span class="p"&gt;|&lt;/span&gt; less --tabs&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; -RFX
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;commit&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;gpgsign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; ~/.gitmessage
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;mergetool &lt;span class="s2"&gt;&amp;#34;nvim&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; nvim &lt;span class="nv"&gt;$MERGED&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;difftool &lt;span class="s2"&gt;&amp;#34;vscode&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; code --wait --diff &lt;span class="nv"&gt;$LOCAL&lt;/span&gt; &lt;span class="nv"&gt;$REMOTE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;mergetool&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;color &lt;span class="s2"&gt;&amp;#34;diff-highlight&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;oldNormal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; red bold
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;oldHighlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; red bold &lt;span class="m"&gt;52&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;newNormal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; green bold
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;newHighlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; green bold &lt;span class="m"&gt;22&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;color &lt;span class="s2"&gt;&amp;#34;diff&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;commit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yellow bold
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;frag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; magenta bold
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; green bold
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; red bold
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;whitespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; red reverse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;push&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;recurseSubmodules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; on-demand
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;gpg&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; gpg2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;filter &lt;span class="s2"&gt;&amp;#34;lfs&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; git-lfs clean -- %f
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;smudge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; git-lfs smudge --skip -- %f
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; git-lfs filter-process --skip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;pull&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;rebase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;credential&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;helper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; store
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;init&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;defaultBranch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;templateDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; ~/.git-templates
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;diff&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; vscode&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The file being referenced/included here, &lt;code&gt;~/Dropbox/.custom/.gitconfig&lt;/code&gt;, looks like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;user&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; email@example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; Your Name
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;signingkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 798034F11B2ADED2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;commit&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;gpgsign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# The following block could be used to update credentials when committing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# depending on what directory is opened. So, if you work for COMPANY_NAME&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# and the pattern &amp;#39;COMPANY_NAME&amp;#39; appears in the current working directory,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# then Git can be smart and update your configuration by including&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# something like the previous block so that the original email, name,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# signingkey and possibly other config options are overwritten.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt; when working at COMPANY_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;includeIf &lt;span class="s2"&gt;&amp;#34;gitdir:**/COMPANY_NAME/**/.git&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; ~/Dropbox/.custom/.gitconfig-COMPANY_NAME&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;So this section briefly points out how custom tools like &lt;code&gt;diff-so-fancy&lt;/code&gt; can be used, how diff output colors can be customized, how specific code editors like Neovim and VS Code can be opened when committing or dealing with merge conflicts and generally just how the file is meant to be structured.&lt;/p&gt;
&lt;p&gt;A lot more on configuring Git can be found in &lt;a href="https://git-scm.com/docs/git-config"&gt;the official documentation on &lt;code&gt;git config&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Being exhaustive when it comes to using Git is definitely not something this article strived to achieve: Git is such a complex piece of software that has been evolving since 2005, when it became a necessity to deal with incoming patches sent to Linus Torvalds to make Linux the fantastic project it has become! There is much, much more to learn, but hopefully this post gives you a slightly different perspective and points the way to a well-rounded journey with the list of references below. Git good!&lt;/p&gt;
&lt;h2 id="more-resources-and-references"&gt;More resources and references&lt;/h2&gt;
&lt;h3 id="websites"&gt;Websites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.conventionalcommits.org/"&gt;Conventional Commits&lt;/a&gt; - &lt;em&gt;&amp;ldquo;A specification for adding human and machine readable meaning to commit messages.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ndpsoftware.com/git-cheatsheet.html"&gt;Git Cheatsheet&lt;/a&gt; - &lt;em&gt;&amp;ldquo;Interactive Git Cheatsheet, categorizing commands based on what they affect.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://gitimmersion.com"&gt;Git Immersion&lt;/a&gt; - &lt;em&gt;&amp;ldquo;A guided tour that walks through the fundamentals of Git, inspired by the premise that to know a thing is to do it.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://githowto.com"&gt;GitHowTo&lt;/a&gt; - &lt;em&gt;&amp;ldquo;Git How To is a guided tour that walks through the fundamentals of Git, inspired by the premise that to know a thing is to do it.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lab.github.com/"&gt;GitHub Learning Lab&lt;/a&gt; - &lt;em&gt;&amp;ldquo;With GitHub Learning Lab, grow your skills by completing fun, realistic projects. Get advice and helpful feedback from our friendly Learning Lab bot.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learngitbranching.js.org"&gt;Learn Git Branching&lt;/a&gt; - &lt;em&gt;&amp;ldquo;An interactive Git visualization tool to educate and challenge!&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/learn/browse/?terms=Github"&gt;Microsoft Learn GitHub modules&lt;/a&gt; - &lt;em&gt;&amp;ldquo;Learn new skills and discover the power of Microsoft products with step-by-step guidance.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/book/en/v2"&gt;Pro Git Book&lt;/a&gt; - &lt;em&gt;&amp;ldquo;The entire Pro Git book, written by Scott Chacon and Ben Straub and published by Apress, is available here. All content is licensed under the Creative Commons Attribution Non Commercial Share Alike 3.0 license. Print versions of the book are available on Amazon.com.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://egghead.io/courses/productive-git-for-developers"&gt;Productive Git for Developers&lt;/a&gt; - &lt;em&gt;&amp;ldquo;You will walk through a series of scenarios which you’ll most commonly encounter in your daily work life as a developer.&amp;rdquo;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dataschool.io/how-to-contribute-on-github/"&gt;Step-by-step guide to contributing on GitHub&lt;/a&gt; - &lt;em&gt;&amp;ldquo;Have you thought about contributing to an open source project, but you&amp;rsquo;re too confused (or intimidated) by the process to even try? I&amp;rsquo;ve been there too!&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://try.github.io"&gt;Try GitHub&lt;/a&gt; - &lt;em&gt;Resources to learn Git: Handbook, cheat sheets, git commands in the browser, etc.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="youtube"&gt;YouTube&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=eulnSXkhE7I"&gt;CS50 Beyond 2019&lt;/a&gt; - CS50&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=SWYqp7iY_Tc"&gt;Git &amp;amp; GitHub Crash Course For Beginners&lt;/a&gt; - Traversy Media&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=RGOj5yH7evk"&gt;Git and GitHub for Beginners - Crash Course&lt;/a&gt; - freeCodeCamp.org&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=HVsySz-h9r4&amp;amp;list=PL-osiE80TeTuRUfjRe54Eea17-YfnOOAx"&gt;Git Tutorial for Beginners: Command-Line Fundamentals&lt;/a&gt; (playlist) - Corey Schafer&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=x0EYpi38Yp4"&gt;GitHub Basics Tutorial - How to Use GitHub&lt;/a&gt; - freeCodeCamp.org&lt;/li&gt;
&lt;li&gt;&lt;a href="https://missing.csail.mit.edu/2020/version-control"&gt;MIT Missing semester (Version Control) - Git data model&lt;/a&gt; - MIT&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=i76ts_0UryI"&gt;The Ultimate Git &amp;amp; GitHub Crash Course - Learn to Version Control for Beginners&lt;/a&gt; - Laith Harb&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="online-courses"&gt;Online courses&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/learning/git-essential-training"&gt;Git Essential Training&lt;/a&gt; (&lt;em&gt;free with free trial&lt;/em&gt;) - LinkedIn Learning. &lt;em&gt;&amp;ldquo;Using a step-by-step approach, author Kevin Skoglund presents the commands that enable efficient code management and reveals the fundamental concepts behind version control systems and the Git architecture. Discover how to track changes to files in a repository, review previous edits, and compare versions of a file; create branches to test new ideas without altering the main project; and merge those changes into the project if they work out.&lt;/em&gt;&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.udemy.com/git-started-with-github/"&gt;Git Started with GitHub&lt;/a&gt; - Udacity. &lt;em&gt;&amp;ldquo;This course is designed to jump right into showing how Git and GitHub work together, focusing on the Git basic workflow. Students can expect to learn the minimum needed to start using Git in about 30 minutes.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eu.udacity.com/course/how-to-use-git-and-github--ud775"&gt;How to Use Git and GitHub&lt;/a&gt; - Udacity. &lt;em&gt;&amp;ldquo;This course, built with input from GitHub, will introduce the basics of using version control by focusing on a particular version control system called Git and a collaboration platform called GitHub.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.udemy.com/short-and-sweet-get-started-with-git-and-github-right-now/"&gt;Short and Sweet: Get Started with Git and GitHub Right Now&lt;/a&gt; - Udemy. &lt;em&gt;&amp;ldquo;In just 30 minutes, this &amp;ldquo;Short and Sweet&amp;rdquo; course covers the essential ideas you need to move forward, without a lot of filler. At the end of the course, you&amp;rsquo;ll be able to set up a GitHub account, install Git, create and configure new Git and GitHub repositories, create a change history for your software projects, and publish your software projects to GitHub.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.udemy.com/the-ultimate-git-5-day-challenge/"&gt;The Ultimate GIT 5-day Challenge&lt;/a&gt; - Udacity. &lt;em&gt;&amp;ldquo;A quick way to determine if working with GIT is something you want to learn more about. This course takes us step-by-step through some basic GIT operations. The course will not dive too deep, and takes small steps on each of five days. As we continue through the course, we learn a basic, single-person workflow that could allow anyone to store files at GitHub or BitBucket.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.coursera.org/learn/version-control-with-git"&gt;Version Control with Git&lt;/a&gt; - Coursera. &lt;em&gt;&amp;ldquo;In this course, you will not learn everything there is to know about Git, but you will build a strong conceptual understanding of the technology, and afterward will be able to confidently dig deeper on any topic that interests you.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.coursera.org/learn/introduction-git-github"&gt;Introduction to Git and Github&lt;/a&gt; - Coursera. Offered by Google. &lt;em&gt;&amp;ldquo;In this course, you’ll learn how to keep track of the different versions of your code and configuration files using a popular version control system (VCS) called Git. We&amp;rsquo;ll also go through how to setup an account with a service called GitHub so that you can create your very own remote repositories to store your code and configuration.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="references-for-this-article"&gt;References for this article&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/git-rerere"&gt;&lt;code&gt;git rerere&lt;/code&gt;&lt;/a&gt; - git-scm.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.loginradius.com/blog/engineering/git-commands/"&gt;35+ Git Commands List Every Programmer Should Know&lt;/a&gt; - loginradius.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Binary_search_algorithm"&gt;Binary search algorithm&lt;/a&gt; - Wikipedia.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gitignore.io/install/command-line"&gt;Gitignore&lt;/a&gt; - Toptal.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hackerrank.com/"&gt;HackerRank&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/"&gt;LeetCode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.atlassian.com/git/tutorials/rewriting-history"&gt;Rewriting history&lt;/a&gt; - Atlassian.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://about.gitlab.com/blog/2021/03/10/new-git-default-branch-name"&gt;The new Git default branch name&lt;/a&gt; - GitLab.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/a/49501436/8787680"&gt;What is git-rerere and how does it work?&lt;/a&gt; - Stack Overflow.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sun, 03 Apr 2022 11:21:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/git-the-gist-of-it-common-commands-for-a-working-workflow/</guid><category>workflow</category><category>git</category><category>productivity</category><category>terminal</category></item><item><title>Managing multiple tmux sessions at once</title><link>https://www.sglavoie.com/posts/managing-multiple-tmux-sessions-at-once/</link><description>Open and close projects with tmux sessions in a breeze via two keyboard shortcuts.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Released back in 2007, &lt;a href="https://github.com/tmux/tmux/wiki"&gt;tmux&lt;/a&gt; keeps being amazing! Because I have never bothered automating anything to make its use simpler, it took some time for it to become a working environment I enjoy for everyday use. This is a thing of the past: thanks to Oleksandr Kocherhin from &lt;a href="https://monsterlessons-academy.com/"&gt;Monsterlessons Academy&lt;/a&gt;, who shared his tmux workflow in his YouTube video titled &lt;a href="https://www.youtube.com/watch?v=GuH7pw9LejY"&gt;Best Terminal Application for Web Development&lt;/a&gt;, I saw a few automation opportunities I thought were worth tackling.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;rsquo;m sharing a little tip about managing tmux sessions that was inspired from the mentioned video. I am using &lt;a href="https://i3wm.org/"&gt;i3wm&lt;/a&gt; on Linux, but you can adapt this with any desktop environment as long as you can trigger the execution of programs with a keyboard shortcut in one way or another. I will be using Rofi and the &lt;code&gt;find&lt;/code&gt; command, which you can find on Unix systems. If you would rather use dmenu instead of Rofi with this script, &lt;a href="https://www.sglavoie.com/posts/using-dmenu-to-optimize-common-tasks/"&gt;this article can serve as a reference&lt;/a&gt;. Enough said, let&amp;rsquo;s get to it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-is-this-all-about"&gt;What is this all about?&lt;/h2&gt;
&lt;p&gt;The idea is simple: have a few keyboard shortcuts to manage tmux sessions. That means being able to open new sessions and being able to kill them too. I kept this setup very simple on purpose but of course some improvements could be made. For instance, it could be nice to know which projects have already been opened so that the script doesn&amp;rsquo;t prompt you with a list of projects that include projects that you have already launched, but I haven&amp;rsquo;t found the need so far to deal with this potential issue as I tend to only work on a couple of projects during the course of a given day.&lt;/p&gt;
&lt;p&gt;In summary, here is what we want to achieve in this post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configure projects so that when we want to open them, they will be set up in the right directory with servers running, etc.;&lt;/li&gt;
&lt;li&gt;Open a tmux session with a keyboard shortcut, listing all projects that were configured in the previous step;&lt;/li&gt;
&lt;li&gt;Kill any opened tmux session with another keyboard shortcut.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s it!&lt;/p&gt;
&lt;h2 id="1-configuring-projects-to-launch-tmux-sessions"&gt;1. Configuring projects to launch tmux sessions&lt;/h2&gt;
&lt;p&gt;Firstly, we need to create new files to open our projects in the way we want. Basically, we need to create a file, let&amp;rsquo;s call it &lt;code&gt;.tmux&lt;/code&gt;, and put it at the root of the project we&amp;rsquo;re interested in working with tmux. Then, we need to put some instructions that will be run with the shell (e.g. Bash or Zsh) so that tmux commands will be run automatically for us and let us &amp;ldquo;attach&amp;rdquo; to the session once all the necessary actions are performed. That means we need to make that file executable. In the terminal, this would be done as follows, assuming the project&amp;rsquo;s folder is the current working directory:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod +x .tmux&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;At last, we want to give some orders to tmux so that when we execute that file, it will open our project in a pre-configured state so we&amp;rsquo;re ready to go. This will need to be customized depending on your needs, but here is a simple example to illustrate what could happen. So, we could put the following inside the &lt;code&gt;.tmux&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -e
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; tmux has-session -t&lt;span class="o"&gt;=&lt;/span&gt;my-project 2&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tmux attach -t my-project
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux new-session -d -s my-project -n nvim -x &lt;span class="k"&gt;$(&lt;/span&gt;tput cols&lt;span class="k"&gt;)&lt;/span&gt; -y &lt;span class="k"&gt;$(&lt;/span&gt;tput lines&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Neovim&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux send-keys -t my-project:nvim &lt;span class="s2"&gt;&amp;#34;n&amp;#34;&lt;/span&gt; Enter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Servers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux new-window -t my-project -n servers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux send-keys -t my-project:servers &lt;span class="s2"&gt;&amp;#34;cd frontend &amp;amp;&amp;amp; yarn start&amp;#34;&lt;/span&gt; Enter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux split-window -t &lt;span class="m"&gt;2&lt;/span&gt; -v -p &lt;span class="m"&gt;50&lt;/span&gt; &lt;span class="c1"&gt;# split it into two halves&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux send-keys -t my-project:servers &lt;span class="s2"&gt;&amp;#34;cd backend &amp;amp;&amp;amp; ap &amp;amp;&amp;amp; uvicorn main:app --reload&amp;#34;&lt;/span&gt; Enter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux attach -t my-project:nvim&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here is what is happening:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If our project, named &lt;code&gt;my-project&lt;/code&gt;, has a tmux session already running, then we &amp;ldquo;attach&amp;rdquo; to it and nothing more is done.&lt;/li&gt;
&lt;li&gt;Otherwise, the session is created in &amp;ldquo;detached&amp;rdquo; mode so we can run a few commands smoothly and later attach to the created session.&lt;/li&gt;
&lt;li&gt;With the &lt;code&gt;-n&lt;/code&gt; flag, we name the window to be created (here, the first window is &lt;code&gt;nvim&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Then, we &amp;ldquo;send&amp;rdquo; keys to the session, to that newly created &lt;code&gt;nvim&lt;/code&gt; window. In this case, we send a single keystroke &lt;code&gt;n&lt;/code&gt;, which is just an alias for executing Neovim.&lt;/li&gt;
&lt;li&gt;In a new window named &lt;code&gt;servers&lt;/code&gt;, we send keys to execute the frontend server.&lt;/li&gt;
&lt;li&gt;Then, we split the second window we just created vertically and we run the commands needed to launch the backend server, again making use of Bash aliases for convenience.&lt;/li&gt;
&lt;li&gt;Finally, we attach to the session and we open the first window &lt;code&gt;nvim&lt;/code&gt;, ready to work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And just like this, what takes quite a few steps to do manually can be automated in one go when setting up the environment at the beginning of a working bout. Now, it could be convenient to create a generic template for the above script to use when creating new projects, especially if those tend to be short-lived and share some properties (e.g. maybe they all need to launch some kind of server in the background).&lt;/p&gt;
&lt;h2 id="2-script-to-open-projects"&gt;2. Script to open projects&lt;/h2&gt;
&lt;p&gt;Firstly, we need a way to list and open all &amp;ldquo;known&amp;rdquo; projects. This is achieved in this example very simply with the &lt;code&gt;find&lt;/code&gt; command as well as with the help of &lt;code&gt;rofi&lt;/code&gt;. The script in question is as follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Get the path to the `.tmux` file to execute using `find` and `rofi`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;find &lt;span class="nv"&gt;$HOME&lt;/span&gt;/dev -type d &lt;span class="se"&gt;\(&lt;/span&gt; -name node_modules -o -name .venv &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o -path name &lt;span class="se"&gt;\)&lt;/span&gt; -prune -false -o -name &lt;span class="s1"&gt;&amp;#39;.tmux&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; rofi -i -dmenu -theme purple -font &lt;span class="s1"&gt;&amp;#39;JetBrainsMono 14&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -width &lt;span class="m"&gt;90&lt;/span&gt; -show window&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Get the full directory path to the `.tmux` file, excluding the filename&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PROJECT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PROJECT&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; xargs -I &lt;span class="o"&gt;{}&lt;/span&gt; dirname &lt;span class="s2"&gt;&amp;#34;{}&amp;#34;&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# If $PROJECT is empty (e.g. pressing Esc at the prompt), don&amp;#39;t do anything&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; ! -z &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kitty --directory &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;So, what this does is this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It finds all the directories where a &lt;code&gt;.tmux&lt;/code&gt; file exists, excluding non-useful folders like &lt;code&gt;node_modules&lt;/code&gt;. The &lt;code&gt;-prune&lt;/code&gt; flag is passed to avoid recursion further inside a directory once a &lt;code&gt;.tmux&lt;/code&gt; file is found. The results of &lt;code&gt;find&lt;/code&gt; are &amp;ldquo;piped&amp;rdquo; into &lt;code&gt;rofi&lt;/code&gt;, which displays the results. User selection is then stored in the &lt;code&gt;PROJECT&lt;/code&gt; variable.&lt;/li&gt;
&lt;li&gt;Once a file is selected, the full path without the filename is extracted and stored in the &lt;code&gt;PROJECT_DIR&lt;/code&gt; variable.&lt;/li&gt;
&lt;li&gt;Finally, all that is left to do is check if the user selected a project and if so, run it with the desired terminal from the correct directory. This will be different for most terminal emulators: here, an example is shown with the &lt;a href="https://sw.kovidgoyal.net/kitty/"&gt;kitty terminal&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="3-script-to-close-projects"&gt;3. Script to close projects&lt;/h2&gt;
&lt;p&gt;Here, we are concerned with the sessions that are now opened and that we might want to close. In a similar fashion as what we did to open projects, we want to kill them with a script that will run Rofi and do the job silently in the background. One such script that will get us there is shown below:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Get the name of all running sessions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TMUX_SESSIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;tmux ls &lt;span class="p"&gt;|&lt;/span&gt; cut -f1 -d &lt;span class="s2"&gt;&amp;#34;:&amp;#34;&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# No listed session, then notify about it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -z &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$TMUX_SESSIONS&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; notify-send &lt;span class="s2"&gt;&amp;#34;No active tmux session found&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Show all running sessions with Rofi and store the selection in a variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SESSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$TMUX_SESSIONS&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; rofi -sep &lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt; -i -dmenu -theme purple -font &lt;span class="s1"&gt;&amp;#39;JetBrainsMono 14&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -width &lt;span class="m"&gt;90&lt;/span&gt; -show window&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Kill the selected session&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux kill-session -t &lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In this case, the usage is quite straightforward: only running sessions are shown, so it&amp;rsquo;s just a matter of choosing the one we want to kill or pressing the &lt;code&gt;Escape&lt;/code&gt; key to abort the Rofi pop-up.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="example-workflow"&gt;Example workflow&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s a matter of setting up keyboard shortcuts to run the scripts to open and kill projects. For instance, one such configuration for the &lt;code&gt;i3&lt;/code&gt; window manager would be as follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+p &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/path/to/projects-start.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+k &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/path/to/projects-kill.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;A simple shortcut lets us choose sessions to open.&lt;/li&gt;
&lt;li&gt;Next, if we decide to kill a session at some point, we have another shortcut to do that.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Far from being a perfect solution (although it already feels comfortable as a main driver), this workflow initiates what I think can lead to substantial optimizations when it comes to using tmux. There&amp;rsquo;s room for improvement when a user selects an already opened session: ideally, we would just switch to that session automatically. These details need to be implemented based on the window manager that&amp;rsquo;s being used, but suffice to say, it can be done! In any case, I hope this may give you a semblance of inspiration to take this idea to the next level. There are tools to help with session persistence, such as &lt;a href="https://github.com/tmux-plugins/tmux-resurrect"&gt;tmux-resurrect&lt;/a&gt;, but that&amp;rsquo;s a topic for another time.&lt;/p&gt;
&lt;h2 id="more-resources-and-references"&gt;More resources and references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=GuH7pw9LejY"&gt;Best Terminal Application for Web Development&lt;/a&gt;, original inspiration for this workflow. — Monsterlessons Academy, YouTube.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmux/tmux/wiki"&gt;The tmux wiki&lt;/a&gt;, GitHub.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmux-plugins/tmux-resurrect"&gt;tmux-resurrect&lt;/a&gt;, GitHub.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sun, 19 Sep 2021 18:13:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/managing-multiple-tmux-sessions-at-once/</guid><category>workflow</category><category>bash</category><category>i3</category><category>rofi</category><category>script</category><category>tmux</category></item><item><title>Managing dotfiles with a Git bare repository</title><link>https://www.sglavoie.com/posts/managing-dotfiles-with-git-bare-repository/</link><description>Keeping configuration files under version control with a Git bare repository is a fast, elegant and convenient solution!</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I have been managing my configuration files for the past year using a technique I originally came across in &lt;a href="https://www.youtube.com/watch?v=tBoLDpTWVOM"&gt;this YouTube video from DistroTube&lt;/a&gt;. This uses a Git bare repository instead of the &amp;ldquo;usual&amp;rdquo; way, which consists of using a normal Git repository where one would store the actual configuration files and create symbolic links to all those files to the expected path on the system.&lt;/p&gt;
&lt;p&gt;Needless to say, symlinks are a pain to manage when potentially hundreds of files need to be tracked in this way. Fortunately, working with a bare repository is much simpler: there&amp;rsquo;s neither a need to move files around nor store any of them in the dotfiles bare repository either. Let&amp;rsquo;s dive in to see how this works! I&amp;rsquo;ll keep the instructions to the point: for more details, please refer to the article &lt;a href="https://www.atlassian.com/git/tutorials/dotfiles"&gt;The best way to store your dotfiles: A bare Git repository&lt;/a&gt; from Atlassian, the source of knowledge behind this post.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-set-up-the-system"&gt;1. Set up the system&lt;/h2&gt;
&lt;p&gt;Open a terminal and brace yourself to type a few commands. This will need to be done only once on any machine you use.&lt;/p&gt;
&lt;p&gt;Set the following variable to the name of the directory where you want to store your dotfiles (e.g. &lt;code&gt;.dotfiles&lt;/code&gt;, &lt;code&gt;dotfiles&lt;/code&gt;, &lt;code&gt;cfg&lt;/code&gt;, etc.):&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DOTFILES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dotfiles&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then type the following commands:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git init --bare &lt;span class="nv"&gt;$HOME&lt;/span&gt;/&lt;span class="nv"&gt;$DOTFILES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/usr/bin/git --git-dir=$HOME/$DOTFILES/ --work-tree=$HOME&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config config --local status.showUntrackedFiles no&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Depending on where you store your terminal aliases (e.g. &lt;code&gt;.bashrc&lt;/code&gt;, &lt;code&gt;.zshrc&lt;/code&gt;, &lt;code&gt;.bash_aliases&lt;/code&gt;, etc.), set the following variable according to your needs:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;ALIASES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.bash_aliases&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Once that is done, type the following command (or open the file containing your aliases with a text editor to manually put the alias wherever you want):&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;alias config=&amp;#39;/usr/bin/git --git-dir=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$DOTFILES&lt;/span&gt;&lt;span class="s2"&gt;/ --work-tree=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/&lt;span class="nv"&gt;$ALIASES&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can of course change the &lt;code&gt;config&lt;/code&gt; alias to something else. I like to simply use &lt;code&gt;c&lt;/code&gt; instead.&lt;/p&gt;
&lt;h2 id="11-create-a-repository-to-host-your-dotfiles"&gt;1.1 Create a repository to host your dotfiles&lt;/h2&gt;
&lt;p&gt;At this point, you will want to make sure you have created an empty Git repository on a website such as &lt;a href="https://github.com/"&gt;GitHub&lt;/a&gt;, &lt;a href="https://gitlab.com/"&gt;GitLab&lt;/a&gt; or &lt;a href="https://bitbucket.org/"&gt;BitBucket&lt;/a&gt;. In the interface of that website, you should find a place that allows you to copy the link to that Git repository, which might look like something like this: &lt;code&gt;git@github.com:username/dotfiles.git&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="12-set-the-remote-to-track-your-dotfiles"&gt;1.2 Set the remote to track your dotfiles&lt;/h2&gt;
&lt;p&gt;With your new Git repository created in the previous step, you will now want to add tracking information to be able to connect to the remote Git repository. You can do so as follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config remote set-url origin git@github.com:username/dotfiles.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For more information about this, you can read the article &lt;a href="https://www.sglavoie.com/posts/setting-up-ssh-and-git-for-multiple-accounts/"&gt;Setting up SSH and Git for Multiple Accounts&lt;/a&gt; if you&amp;rsquo;ll be using an SSH key to connect and more specifically &lt;a href="https://git-scm.com/docs/git-remote"&gt;the official documentation on the &lt;code&gt;git remote&lt;/code&gt; command&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="2-use-the-new-system"&gt;2. Use the new system&lt;/h2&gt;
&lt;h2 id="21-on-your-current-machine"&gt;2.1 On your current machine&lt;/h2&gt;
&lt;p&gt;Tracking changes will now be a breeze! All we have to do is to add files with our new alias (assuming &lt;code&gt;config&lt;/code&gt; was chosen) to Git, commit and push to a remote repository to keep everything neatly backed up.&lt;/p&gt;
&lt;p&gt;A typical workflow will look like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config add .gitconfig
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config commit -m &lt;span class="s2"&gt;&amp;#34;Add .gitconfig&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config add .zshrc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config commit -m &lt;span class="s2"&gt;&amp;#34;Add .zshrc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config push&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="22-on-a-different-machine"&gt;2.2 On a different machine&lt;/h2&gt;
&lt;p&gt;Once yet have set up the system once with a remote repository, then the process becomes simpler as you can clone that repository and save a few steps. Here&amp;rsquo;s what you&amp;rsquo;d be expected to do to set up the same dotfiles management system on another machine:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# set to different values as desired&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DOTFILES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dotfiles
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;REMOTE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;git@github.com:username/dotfiles.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/usr/bin/git --git-dir=$HOME/$DOTFILES/ --work-tree=$HOME&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DOTFILES&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.gitignore
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone --bare &lt;span class="nv"&gt;$REMOTE&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/&lt;span class="nv"&gt;$DOTFILES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config checkout&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;At this point, you might see an error message if you have existing configuration files on your machine in locations that are already tracked by Git in your dotfiles on the remote repository. If that&amp;rsquo;s the case, you can move them to a secure place like so:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p .config-backup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config checkout 2&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; egrep &lt;span class="s2"&gt;&amp;#34;\s+\.&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;print $1&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;xargs -I&lt;span class="o"&gt;{}&lt;/span&gt; mv &lt;span class="o"&gt;{}&lt;/span&gt; .config-backup/&lt;span class="o"&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let&amp;rsquo;s make sure we don&amp;rsquo;t show all untracked files again:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config config --local status.showUntrackedFiles no&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;From here, this will be the same workflow as described in the section &lt;a href="#21-on-your-current-machine"&gt;2.1 On your current machine&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;No more symlinks: this is definitely a nicer approach! You will need to be a bit more familiar with Git if you need to edit your Git history such as with the &lt;code&gt;rebase&lt;/code&gt; sub-command or when adding changes selectively such as with the sub-command &lt;code&gt;add -p&lt;/code&gt; to add files interactively — which might be the case if you want to commit only part of the changes made to one file — but that&amp;rsquo;s a great tool to master anyways, and there&amp;rsquo;s an article to get the ball rolling: &lt;a href="https://www.sglavoie.com/posts/git-the-gist-of-it-common-commands-for-a-working-workflow/"&gt;Git the gist of it: common commands for a working workflow&lt;/a&gt; ;).&lt;/p&gt;
&lt;h2 id="more-resources-and-references"&gt;More resources and references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=tBoLDpTWVOM"&gt;Git Bare Repository - A Better Way To Manage Dotfiles&lt;/a&gt; — DistroTube, YouTube&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.atlassian.com/git/tutorials/dotfiles"&gt;The best way to store your dotfiles: A bare Git repository&lt;/a&gt;, the source of the idea for this post. — Atlassian&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sun, 30 May 2021 13:13:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/managing-dotfiles-with-git-bare-repository/</guid><category>workflow</category><category>dotfiles</category><category>git</category><category>terminal</category></item><item><title>Setting up SSH and Git for Multiple Accounts</title><link>https://www.sglavoie.com/posts/setting-up-ssh-and-git-for-multiple-accounts/</link><description>Simple workflow with SSH, GPG and Git to work remotely with multiple accounts in a convenient way.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;To work effectively with services such as GitHub and GitLab, it is useful to set up a workflow that doesn&amp;rsquo;t get in the way, especially when multiple accounts are involved. SSH will be set up to avoid entering the username/password combination every time we interact with remote repositories and Git will be set up to work differently for each account while signing the commits potentially with different GPG keys.&lt;/p&gt;
&lt;p&gt;In short, we need to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up SSH locally (in &lt;code&gt;~/.ssh/&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;Set up SSH keys remotely (GitLab, GitHub, Bitbucket, etc.);&lt;/li&gt;
&lt;li&gt;Set up Git locally (in &lt;code&gt;~/.gitconfig&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;Set up GPG keys remotely (add our keys(s) to GitLab, GitHub, etc.);&lt;/li&gt;
&lt;li&gt;Start interacting with remote repositories.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="setting-ssh-locally"&gt;Setting SSH locally&lt;/h2&gt;
&lt;p&gt;First, let&amp;rsquo;s make sure we have some SSH keys to work with. The default location is &lt;code&gt;~/.ssh/id_rsa&lt;/code&gt; (you can just press &lt;code&gt;Enter&lt;/code&gt; when asked to save to a path):&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;ssh-keygen -t rsa -C &lt;span class="s2"&gt;&amp;#34;email@personal.com&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The next key should have a different path:&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;ssh-keygen -t rsa -C &lt;span class="s2"&gt;&amp;#34;email@work.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;Enter file in which to save the key
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;/home/sglavoie/.ssh/id_rsa&lt;span class="o"&gt;)&lt;/span&gt;: /home/sglavoie/.ssh/id_rsa_work&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Add the keys to the authentication agent like so:&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;ssh-add /.ssh/id_rsa
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;ssh-add /.ssh/id_rsa_work&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;See which keys were added:&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;ssh-add -l &lt;span class="c1"&gt;# list the keys&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you need to delete any keys that were cached prior to that:&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;ssh-add -D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now, we need a configuration file:&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;touch ~/.ssh/config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;chmod &lt;span class="m"&gt;600&lt;/span&gt; ~/.ssh/config&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let&amp;rsquo;s add some content in there, assuming we deal with GitHub and GitLab, both with a personal account and a work account:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AddKeysToAgent yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host github
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HostName github.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; IdentityFile ~/.ssh/id_rsa
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host github-work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HostName github.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; IdentityFile ~/.ssh/id_rsa_work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host gitlab
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HostName gitlab.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; IdentityFile ~/.ssh/id_rsa
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host gitlab-work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HostName gitlab.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; IdentityFile ~/.ssh/id_rsa_work&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The directive &lt;code&gt;AddKeysToAgent yes&lt;/code&gt; is useful to avoid typing &lt;code&gt;ssh-add path_to_key&lt;/code&gt; every time SSH is needed.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Host&lt;/code&gt; can have any name we want, it doesn&amp;rsquo;t need to match the &lt;code&gt;HostName&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;HostName&lt;/code&gt; is the address we need to access. This should be the same thing for all accounts using a particular service (here, GitHub or GitLab).&lt;/li&gt;
&lt;li&gt;We can set the user to be &lt;code&gt;git&lt;/code&gt; by default.&lt;/li&gt;
&lt;li&gt;For each &lt;code&gt;Host&lt;/code&gt;, we indicate which &lt;code&gt;IdentityFile&lt;/code&gt; to use when trying to work with a remote repository.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="automatically-load-the-keys-from-the-shell"&gt;Automatically load the keys from the Shell&lt;/h2&gt;
&lt;p&gt;For Zsh, the following can be added near the top of &lt;code&gt;~/.zshrc&lt;/code&gt; when using the &lt;code&gt;ssh-agent&lt;/code&gt; plugin:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Load multiple SSH keys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zstyle :omz:plugins:ssh-agent identities id_rsa id_rsa_work&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With &lt;a href="https://ohmyz.sh/"&gt;Oh-My-Zsh&lt;/a&gt;, the &lt;code&gt;ssh-agent&lt;/code&gt; plugin should be contained in &lt;code&gt;plugins&lt;/code&gt; like so:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;git ssh-agent fzf gitignore zsh-autosuggestions history-substring-search&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Somewhere below&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="nv"&gt;$ZSH&lt;/span&gt;/oh-my-zsh.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With this setup, the SSH keys will be loaded when opening a terminal after booting up and those will be available for any subsequent terminal sessions until the user session is exited.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="setting-ssh-keys-remotely"&gt;Setting SSH keys remotely&lt;/h2&gt;
&lt;p&gt;The process will be slightly different on each platform on which we want to authenticate, but the gist of it is to paste the content of the public SSH key in the field when asked to do so.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/help/ssh/README"&gt;Instructions for GitLab&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/connecting-to-github-with-ssh"&gt;Instructions for GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.atlassian.com/bitbucket-cloud/docs/set-up-an-ssh-key/"&gt;Instructions for Bitbucket&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="setting-git-locally"&gt;Setting Git locally&lt;/h2&gt;
&lt;p&gt;The idea is to have a &lt;code&gt;~/.gitconfig&lt;/code&gt; file from which we load the main Git configuration by default (let&amp;rsquo;s say, our personal account) and then we load another account – overwriting the Git settings of the personal account with the settings defined for that other account – whenever we navigate to a directory that relates to that other account. Let&amp;rsquo;s see this in action.&lt;/p&gt;
&lt;h2 id="minimal-gitconfig"&gt;Minimal &lt;code&gt;~/.gitconfig&lt;/code&gt;&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# default configuration settings to load&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;include&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; ~/.gitconfig-personal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# when working with company-x&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# those settings are loaded only when the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# directory matches the pattern defined&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;includeIf &lt;span class="s2"&gt;&amp;#34;gitdir:**/company-x/**/.git&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; ~/.gitconfig-company-x
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;gpg&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nv"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; gpg
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;credential&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nv"&gt;helper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; store&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;[credential]&lt;/code&gt; section with the setting &lt;code&gt;helper = store&lt;/code&gt; will store your username/password combination when using HTTPS so you don&amp;rsquo;t have to type it over and over again. You could also set this to &lt;code&gt;helper = cache&lt;/code&gt; if you don&amp;rsquo;t want to permanently store credentials.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;includeIf&lt;/code&gt; directive will be triggered whenever you are in a directory containing &lt;code&gt;company-x&lt;/code&gt; in this case so that your correct GPG key and Git settings will be used instead of the default settings for your personal account.&lt;/li&gt;
&lt;li&gt;In the block &lt;code&gt;[gpg]&lt;/code&gt;, your system may be using the program &lt;code&gt;gpg2&lt;/code&gt; instead of &lt;code&gt;gpg&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="minimal-gitconfig-personal-and-gitconfig-company-x"&gt;Minimal &lt;code&gt;~/.gitconfig-personal&lt;/code&gt; and &lt;code&gt;~/.gitconfig-company-x&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Example for one of those:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;user&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; email@work.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; Sébastien Lavoie
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;signingkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; A343702EBE11E0C2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;commit&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;gpgsign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you don&amp;rsquo;t have a GPG key already, you can generate one with this command:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpg --gen-key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To list existing GPG keys to determine the &lt;code&gt;signinkey&lt;/code&gt; value to use in those files, you can type:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpg --list-secret-keys --keyid-format LONG&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You might get an output similar to the following (this one is showing only one key):&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sec rsa3072/A343702EBE11E0C2 2020-10-03 &lt;span class="o"&gt;[&lt;/span&gt;SC&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;expires: 2022-10-03&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; EF731EFC008D47D176C05910A343702EBE11E0C2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uid &lt;span class="o"&gt;[&lt;/span&gt;ultimate&lt;span class="o"&gt;]&lt;/span&gt; Sébastien Lavoie &amp;lt;email@work.com&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssb rsa3072/718726CCFED43B47 2020-10-03 &lt;span class="o"&gt;[&lt;/span&gt;E&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;expires: 2022-10-03&lt;span class="o"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The bit you need to retrieve for the &lt;code&gt;signingkey&lt;/code&gt; value comes after the type of encryption, here it&amp;rsquo;s &lt;code&gt;rsa3072&lt;/code&gt; and the bit we want is &lt;code&gt;A343702EBE11E0C2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you need to edit a key, there are plenty of options described with &lt;code&gt;man gpg&lt;/code&gt; or &lt;code&gt;man gpg2&lt;/code&gt;. For instance, to remove the expiration date for the above key:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpg2 --edit-key EF731EFC008D47D176C05910A343702EBE11E0C2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;At the &lt;code&gt;gpg&amp;gt;&lt;/code&gt; prompt, type &lt;code&gt;expire&lt;/code&gt; and follow the instructions.&lt;/p&gt;
&lt;p&gt;To delete a key, you can do so by referring to the email address like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpg --delete-secret-and-public-key email@work.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Just follow the instructions from there. You may need to repeat the process multiple times if your email address is associated with more than one key.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="setting-gpg-keys-remotely"&gt;Setting GPG keys remotely&lt;/h2&gt;
&lt;p&gt;Just like with the SSH keys, the process differs from one platform to the other.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.gitlab.com/ee/user/project/repository/gpg_signed_commits/"&gt;Instructions for GitLab&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/adding-a-new-gpg-key-to-your-github-account"&gt;Instructions for GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://confluence.atlassian.com/bitbucketserver/using-gpg-keys-913477014.html"&gt;Instructions for Bitbucket&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="interacting-with-remote-repositories"&gt;Interacting with remote repositories&lt;/h2&gt;
&lt;p&gt;Now that the Git configuration is set up and we have SSH and GPG keys to authenticate ourselves and verify our identity when committing, respectively, we can start interacting with remote repositories. From the example we have been following, the file &lt;code&gt;~/.gitconfig-personal&lt;/code&gt; will be used by default (and by consequence, our personal account).&lt;/p&gt;
&lt;h2 id="the-ssh-part"&gt;The SSH part&lt;/h2&gt;
&lt;p&gt;When first cloning, change the host so that it reflects what you have in &lt;code&gt;~/.ssh/id_rsa_correct_key_file&lt;/code&gt;. For your personal account, no change would be required:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone git@github.com:organization/repo.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For a repository at work requiring the SSH key set up for the work account, you would need to change to the appropriate host like so (we still use the &lt;code&gt;git&lt;/code&gt; user for convenience):&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone git@github-work:organization/repo.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For a refresher, the following are the hosts we have set in &lt;code&gt;~/.ssh/config&lt;/code&gt;: &lt;code&gt;github&lt;/code&gt;, &lt;code&gt;github-work&lt;/code&gt;, &lt;code&gt;gitlab&lt;/code&gt;, &lt;code&gt;gitlab-work&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The difference will be noticed when pushing/pulling as seen with &lt;code&gt;git remote -v&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;git remote -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;origin	git@github-work:organization/repo.git &lt;span class="o"&gt;(&lt;/span&gt;fetch&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;origin	git@github-work:organization/repo.git &lt;span class="o"&gt;(&lt;/span&gt;push&lt;span class="o"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Whereas the personal account will have the same host as usual, for instance &lt;code&gt;git@github.com:organization/repo.git&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-gpg-part"&gt;The GPG part&lt;/h2&gt;
&lt;p&gt;If we want to keep our personal and work Git configurations separate (and we probably want that! ;)), it&amp;rsquo;s only a matter of ensuring that the &lt;code&gt;includeIf&lt;/code&gt; pattern contains, in this example, &lt;code&gt;company-x&lt;/code&gt; somewhere in the path. When this is the case, we will see with &lt;code&gt;git config --list&lt;/code&gt; in the cloned repository that our personal account details are loaded first, but if the &lt;code&gt;includeIf&lt;/code&gt; directive matches, the settings for that other account will be applied on top and used when committing.&lt;/p&gt;
&lt;p&gt;If you type &lt;code&gt;git config --list&lt;/code&gt; and search for the word &amp;ldquo;email&amp;rdquo; in the output, it will appear only once when the default &lt;code&gt;~/.ssh/id_rsa&lt;/code&gt; key is used (or whatever is read for the &lt;code&gt;IdentityFile&lt;/code&gt; from the SSH configuration file) while you will see that same personal email showing up first in a work repository, but then later in the output you will find the work email, work GPG key and so on.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This is one possible kind of setup we can use to work with SSH and GPG comfortably. This is pretty much a &amp;ldquo;set it and forget it&amp;rdquo; approach as long as you remember the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SSH&lt;/strong&gt;: Change the SSH host when cloning. If the repository is not publicly available, it would fail anyways (or you may realize you can clone it if it&amp;rsquo;s a public repo but have no &lt;code&gt;push&lt;/code&gt; access).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git&lt;/strong&gt;: Make sure you are in a directory where the &lt;code&gt;includeIf&lt;/code&gt; directive will kick in to set up the email, GPG key and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One nice tip to help with the latter bullet point could be to define an alias, say in &lt;code&gt;~/.bash_aliases&lt;/code&gt;, something like what follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;cdwork&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cd /path/to/work/dir/with/appropriate/pattern&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then, you simply &lt;code&gt;cdwork&lt;/code&gt; and store all your Git repositories for work under that root repository, which would always match the desired pattern by default. That&amp;rsquo;s it. Anything else related to the personal account can be cloned anywhere on the file system since this is the default configuration file used. For more Git-related content, you might enjoy reading &lt;a href="https://www.sglavoie.com/posts/git-the-gist-of-it-common-commands-for-a-working-workflow/"&gt;Git the gist of it: common commands for a working workflow&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sat, 03 Oct 2020 15:25:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/setting-up-ssh-and-git-for-multiple-accounts/</guid><category>workflow</category><category>ssh</category><category>git</category><category>gpg</category></item><item><title>Black: A Python Formatter to Brighten Your Workflow</title><link>https://www.sglavoie.com/posts/black-a-python-formatter-to-brighten-your-workflow/</link><description>Meet Black, a superb Python code formatter that will automatically reformat your code in accordance to PEP-8 standards.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="what-is-black"&gt;What is Black?&lt;/h2&gt;
&lt;p&gt;From its &lt;a href="https://github.com/python/black"&gt;GitHub repository&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Black is the uncompromising Python code formatter. By using it, you agree to cede control over minutiae of hand-formatting. In return, Black gives you speed, determinism, and freedom from pycodestyle nagging about formatting. You will save time and mental energy for more important matters.&lt;/p&gt;

&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;Black&lt;/strong&gt; will take less than ideal code and will automatically reformat it according to the &lt;a href="https://www.python.org/dev/peps/pep-0008/"&gt;PEP-8 style guide&lt;/a&gt;. It will take care of many things, including the following (taken in that order from &lt;code&gt;README.md&lt;/code&gt; in &lt;strong&gt;Black&lt;/strong&gt;&amp;rsquo;s repo):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wrapping lines properly by shortening as much as possible single statements;&lt;/li&gt;
&lt;li&gt;Break long lines;&lt;/li&gt;
&lt;li&gt;Remove superfluous empty lines;&lt;/li&gt;
&lt;li&gt;Take care of adding trailing commas where necessary;&lt;/li&gt;
&lt;li&gt;Convert single quotes (&lt;code&gt;'&lt;/code&gt;) to double quotes (&lt;code&gt;&amp;quot;&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;Convert numeric literals to lowercase (&lt;code&gt;0XAB&lt;/code&gt; to &lt;code&gt;0xAB&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;Superfluous parentheses are removed;&lt;/li&gt;
&lt;li&gt;Split call chains on different lines;&lt;/li&gt;
&lt;li&gt;Works from the terminal and support many code editor integrations (Emacs, Vim, VS Code, Sublime, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that it&amp;rsquo;s clear &lt;em&gt;why&lt;/em&gt; it can be a good idea to use it, let&amp;rsquo;s see how it works with Vim and the terminal.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="how-to-use-it"&gt;How to use it?&lt;/h2&gt;
&lt;p&gt;First, we need to install it using Python 3.6.0+:&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;pip install black&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;That&amp;rsquo;s it!&lt;/p&gt;
&lt;h3 id="use-it-from-the-terminal"&gt;Use it from the terminal&lt;/h3&gt;
&lt;p&gt;In its simplest invocation, we can use &lt;strong&gt;Black&lt;/strong&gt; by specifying a file or a directory by typing:&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;black path_to_file_or_directory&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It doesn&amp;rsquo;t come with many options as its goal is to automatically format code consistently, but you can find out more about this tool in the following way:&lt;/p&gt;
&lt;div class="code-block" data-prompted&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="code-block__prompt"&gt;$ &lt;/span&gt;black --help&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="use-it-with-vim"&gt;Use it with Vim&lt;/h3&gt;
&lt;p&gt;Using &lt;strong&gt;Black&lt;/strong&gt; as part of your workflow in a code editor is where I believe it really shines, because you can see almost in real-time when you make a formatting mistake, which will help down the road in committing less sins.&lt;/p&gt;
&lt;p&gt;Integrating &lt;strong&gt;Black&lt;/strong&gt; with Vim or Neovim is quick and painless, adding a simple line in your &lt;code&gt;.vimrc&lt;/code&gt; or &lt;code&gt;init.vim&lt;/code&gt; file. You can also proceed to do a manual installation without a plugin manager as explained in the GitHub repository.&lt;/p&gt;
&lt;p&gt;With &lt;a href="https://github.com/junegunn/vim-plug"&gt;vim-plug&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;vim&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Plug&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python/black&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With &lt;a href="https://github.com/VundleVim/Vundle.vim"&gt;Vundle&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;vim&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Plugin&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python/black&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Once the plugin is installed, it may well be convenient to automatically format Python files when saving the buffer, which can be accomplished by adding the following line to the configuration file:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;vim&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;autocmd&lt;/span&gt; &lt;span class="nx"&gt;BufWritePre&lt;/span&gt; *.&lt;span class="nx"&gt;py&lt;/span&gt; &lt;span class="k"&gt;execute&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;:Black&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;By default, &lt;strong&gt;Black&lt;/strong&gt; works surprisingly well on its own. I only went ahead and changed the default line length from 88 to 79 characters by also adding this line:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;vim&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;:&lt;span class="nx"&gt;black_linelength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;79&lt;/span&gt; &lt;span class="c"&gt;&amp;#34; default is 88&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Finally, it&amp;rsquo;s worth noting you can update &lt;strong&gt;Black&lt;/strong&gt; at any point from the terminal with &lt;code&gt;pip&lt;/code&gt; and you can also do it directly from within Vim/Neovim with this command:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;vim&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;BlackUpgrade&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Vim will then output something along those lines:&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Upgrading Black with pip&amp;hellip;&lt;/p&gt;

&lt;/blockquote&gt;&lt;blockquote&gt;
 &lt;p&gt;DONE! You are all set, thanks for waiting&lt;/p&gt;

&lt;/blockquote&gt;&lt;h2 id="any-example"&gt;Any example?&lt;/h2&gt;
&lt;p&gt;Taking a random Python 3.6+ code sample, we could originally have something like this (hopefully not):&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;python&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data_sources&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Retrieve a list of all matching log files in `source`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;log_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;LOG_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;*&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;log_files&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;There is no log file to delete in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Log files in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;log_file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGUMENTS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remind&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;REMINDER_IS_SET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;log_file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;Do you want to delete log files &amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;for this source? (y/n) &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_says_yes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;log_file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Log files deleted.&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Exiting script...&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Running &lt;strong&gt;Black&lt;/strong&gt;, we will get something that&amp;rsquo;s quite a bit more palatable:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;python&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data_sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Retrieve a list of all matching log files in `source`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;log_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;LOG_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;log_files&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;There is no log file to delete in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Log files in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;log_file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ARGUMENTS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;REMINDER_IS_SET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Do you want to delete log files &amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;for this source? (y/n) &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_says_yes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;log_file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Log files deleted.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Exiting script...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I think the results speak for themselves: enforcing a style with a tool like &lt;strong&gt;Black&lt;/strong&gt; is definitely useful to avoid arguments about conflicting coding styles. It lets you focus on what matters instead: being productive while providing value to the world.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sat, 13 Jul 2019 13:10:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/black-a-python-formatter-to-brighten-your-workflow/</guid><category>workflow</category><category>neovim</category><category>plugin</category><category>productivity</category><category>python</category><category>terminal</category><category>vim</category></item><item><title>fzf - A Fuzzy Finder to Accomplish Anything</title><link>https://www.sglavoie.com/posts/fzf-a-fuzzy-finder-to-accomplish-anything/</link><description>Search and, most importantly, finally find what you are looking for on your machine. If you rely on the terminal a lot, fzf may well be capable of speeding up your productivity!</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I know people who make such a mess when &lt;em&gt;not&lt;/em&gt; organizing their files and
directories appropriately on their system, they would benefit greatly
from &lt;code&gt;fzf&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="what-is-fzf"&gt;What is fzf?&lt;/h2&gt;
&lt;p&gt;From the &lt;a href="https://github.com/junegunn/fzf"&gt;official GitHub page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;fzf is a general-purpose command-line fuzzy finder. It&amp;rsquo;s an
interactive Unix filter for command-line that can be used with any list;
files, command history, processes, hostnames, bookmarks, git commits,
etc.&lt;/p&gt;

&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="terminal-aliases"&gt;Terminal aliases&lt;/h2&gt;
&lt;p&gt;One of the main uses of &lt;code&gt;fzf&lt;/code&gt; is from the terminal. Being so flexible
to use, it can be combined with all kinds of commands with the help
of pipes (&lt;code&gt;|&lt;/code&gt;) to bend it to your desires. Here are some aliases I am
currently using that undoubtedly improve my terminal workflow.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;##### Functions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Select a configuration file with fzf and open it with Neovim&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;conf&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; du -a ~/.dotfiles/* ~/.config/* &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $2}&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; fzf &lt;span class="p"&gt;|&lt;/span&gt; xargs -r nvim &lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Select a file from current folder and recursively with fzf and open it with Neovim&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;se&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; du -a ./* &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $2}&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; fzf &lt;span class="p"&gt;|&lt;/span&gt; xargs -r nvim &lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Select a file recursively from university folder with fzf and open it with default app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sc&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; du -a ~/Dropbox/university/* &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $2}&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; fzf &lt;span class="p"&gt;|&lt;/span&gt; xargs -r xdg-open &lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The first alias, &lt;code&gt;conf&lt;/code&gt; (short for &lt;em&gt;configuration&lt;/em&gt;), allows to search
only within the two folders specified for configuration files, which
makes it pop almost instantaneously since it doesn&amp;rsquo;t have to scan files
scattered anywhere else. You can then type anything that partially
matches a file path and even include slashes (&lt;code&gt;/&lt;/code&gt;) in your match if you
know in which directories to look. You can then type &lt;code&gt;Enter&lt;/code&gt; to open the
file with your favorite text editor (here set to &lt;code&gt;nvim&lt;/code&gt;) or type &lt;code&gt;CTRL + c&lt;/code&gt; to abort the command.&lt;/p&gt;
&lt;p&gt;The other aliases work in a similar fashion. &lt;code&gt;se&lt;/code&gt; (short for &lt;em&gt;search&lt;/em&gt;)
will simply search recursively for any kind of files in the current
directory and open the selected file in a text editor. Of course,
certain file types can be excluded and everything else can be tweaked
with more piping power. In the case of the third alias, &lt;code&gt;sc&lt;/code&gt; (short for
&lt;em&gt;school&lt;/em&gt;), it will also search recursively for any kind of file in the
&lt;code&gt;university&lt;/code&gt; folder and will open it with the default application set to
open that kind of file (video, image, text, PDF, etc.).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="vimneovim-integration"&gt;Vim/Neovim integration&lt;/h2&gt;
&lt;p&gt;Using &lt;a href="https://github.com/junegunn/vim-plug"&gt;Vim-Plug&lt;/a&gt; from the same
author as &lt;code&gt;fzf&lt;/code&gt; is as easy as adding the following to Vim/Neovim&amp;rsquo;s
configuration file. First, we make sure that &lt;code&gt;fzf&lt;/code&gt; is available on the
system and install it if it&amp;rsquo;s not. Then, we install the &lt;code&gt;fzf.vim&lt;/code&gt; plugin
to integrate fzf in Vim and optionally, we may configure more options so
that &lt;code&gt;fzf&lt;/code&gt; can be used more optimally, which can be found on the &lt;a href="https://github.com/junegunn/fzf.vim"&gt;GitHub
plugin page&lt;/a&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;vim&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Plug&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;junegunn/fzf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; { &lt;span class="s1"&gt;&amp;#39;dir&amp;#39;&lt;/span&gt;: $&lt;span class="nx"&gt;HOME&lt;/span&gt; . &lt;span class="s1"&gt;&amp;#39;/.fzf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;do&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;./install --all&amp;#39;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Plug&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;junegunn/fzf.vim&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;#34;&amp;#34;&amp;#34;&amp;#34;&amp;#34; [ FZF ]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;#34; Allows fzf to ignore patterns in .gitignore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;let&lt;/span&gt; $&lt;span class="nx"&gt;FZF_DEFAULT_COMMAND&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ag -g &amp;#34;&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;#34; Mapping selecting mappings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;nmap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;leader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;plug&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;fzf&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;xmap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;leader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;plug&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;fzf&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;omap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;leader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;plug&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;fzf&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;#34; Insert mode completion&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;imap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;plug&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;fzf&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;imap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;plug&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;fzf&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;imap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;plug&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;fzf&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;imap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;plug&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;fzf&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;#34; Advanced customization using autoload functions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;#34; (expand word completing window)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;inoremap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fzf&lt;/span&gt;#&lt;span class="nx"&gt;vim&lt;/span&gt;#&lt;span class="nx"&gt;complete&lt;/span&gt;#&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;{&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;20%&amp;#39;&lt;/span&gt;}&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;#34; Make use of fzf command instead of CtrlP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; :&lt;span class="nx"&gt;FZF&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;#34;&amp;#34;&amp;#34; [ / FZF ]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="working-with-neovim"&gt;Working with Neovim&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fzf&lt;/code&gt; really shines when used with Neovim as it is extremely fast,
especially when configured with
&lt;a href="https://github.com/ggreer/the_silver_searcher"&gt;The Silver Searcher&lt;/a&gt;
which deserves its own article. It
integrates with core functionality of Vim and makes it easy to find what
you are looking for.&lt;/p&gt;
&lt;p&gt;From the above configuration, for instance:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;vim&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;nmap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;leader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;plug&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;fzf&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This allows to search for existing mappings and commands, which can be
faster than diving in the help pages when looking for a quick reference.&lt;/p&gt;
&lt;p&gt;This one in insert mode is very handy:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;vim&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;imap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;plug&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;fzf&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Without affecting keyword completion with &lt;code&gt;CTRL + n&lt;/code&gt; and &lt;code&gt;CTRL + p&lt;/code&gt;
(unless you have &lt;code&gt;set complete+=k&lt;/code&gt; in your configuration file), you can
complete words from a custom dictionary of your choice with &lt;code&gt;CTRL + x CTRL + k&lt;/code&gt;. The window that appears can be moved and resized, which is
what is happening here:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;vim&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;inoremap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fzf&lt;/span&gt;#&lt;span class="nx"&gt;vim&lt;/span&gt;#&lt;span class="nx"&gt;complete&lt;/span&gt;#&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;{&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;20%&amp;#39;&lt;/span&gt;}&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In the same way, file paths can be completed from the current working
directory &lt;code&gt;CTRL + x CTRL + f&lt;/code&gt; and existing lines can be quickly inserted
with &lt;code&gt;CTRL + x CTRL + l&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.sglavoie.com/images/posts/0016_a_fuzzy_file_finder_to_accomplish_anything/fzf_autocompletion.png"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0016_a_fuzzy_file_finder_to_accomplish_anything/fzf_autocompletion.png" alt="fzf_line_autocompletion" class="max-size-img-post"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Finally, I use &lt;code&gt;fzf&lt;/code&gt; to open any file quickly from the working directory
inside Neovim with the mapping &lt;code&gt;CTRL + p&lt;/code&gt;, which replaces the CtrlP
plugin and can work much faster on larger codebases.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There is much more that can be done with it and I am barely scratching
the surface here. Luke Smith shared
&lt;a href="https://www.youtube.com/watch?v=vt33Hp-4RXg"&gt;a great video&lt;/a&gt; that will
complement the information from this post nicely with a more technical
approach. Highly recommended to see more practical ways to use this
superb piece of software!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lukesmith.xyz/"&gt;Luke Smith&lt;/a&gt; also makes a strong point of
combining &lt;code&gt;fzf&lt;/code&gt; with &lt;a href="https://tools.suckless.org/dmenu/"&gt;dmenu&lt;/a&gt;, a tool
that I described succinctly in
&lt;a href="https://www.sglavoie.com/posts/suckless-minimalist-tools-that-work-great/"&gt;a previous article&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Fri, 24 May 2019 10:56:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/fzf-a-fuzzy-finder-to-accomplish-anything/</guid><category>workflow</category><category>plugin</category><category>productivity</category><category>shell</category><category>terminal</category></item><item><title>Using i3 as a Window Manager for Increased Productivity</title><link>https://www.sglavoie.com/posts/using-i3-as-a-window-manager-for-increased-productivity/</link><description>I was striving to improve my productivity as one of my New Year’s resolutions and finally found the courage to take the time to try out i3, a fantastic window manager.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="what-is-i3"&gt;What is i3?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://i3wm.org/"&gt;i3&lt;/a&gt; is a tiling window manager. To get to know &lt;code&gt;i3&lt;/code&gt;
better, it&amp;rsquo;s good to situate what desktop environments are in the first
place.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="what-are-desktop-environments"&gt;What are desktop environments?&lt;/h3&gt;
&lt;p&gt;If you are familiar with Linux,
you certainly know about desktop environments such as
&lt;a href="https://www.gnome.org/"&gt;GNOME&lt;/a&gt;, &lt;a href="https://www.kde.org/"&gt;KDE&lt;/a&gt;,
&lt;a href="https://www.xfce.org/"&gt;Xfce&lt;/a&gt;&amp;hellip; and actually many
more are available (you can see &lt;a href="https://en.wikipedia.org/wiki/Desktop_environment"&gt;this article on
Wikipedia&lt;/a&gt;
for an extended selection)! The classic Graphical User Interface
(GUI) on Windows XP was known as
&lt;a href="https://en.wikipedia.org/wiki/Luna_%28theme%29"&gt;Luna&lt;/a&gt; and is now
called Modern on Windows 10.
&lt;a href="https://en.wikipedia.org/wiki/Aqua_%28user_interface%29"&gt;Aqua&lt;/a&gt; is the
name of the GUI on macOS. Well, every desktop environment comes with
a window manager, the main component that makes interactions with
windows possible, like moving, resizing, minimizing or closing them.&lt;/p&gt;
&lt;p&gt;Desktop environments are very useful in many ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They are usually very easy to set up as they come fully functional out
of the box. Like opening a user session on macOS or Windows.&lt;/li&gt;
&lt;li&gt;They are sometimes quite customizable, but you don&amp;rsquo;t have to change
anything to get them to work, as they normally set default applications
to open with certain file types.&lt;/li&gt;
&lt;li&gt;They automatically integrate services such as WiFi network detection,
automatic mounting of drives when you plug them in, etc.&lt;/li&gt;
&lt;li&gt;You can enable &lt;a href="https://en.wikipedia.org/wiki/Virtual_desktop"&gt;virtual
desktops&lt;/a&gt;
to split your work
across different spaces. This concept can grow to
&lt;em&gt;[Activities](&lt;a href="https://docs.kde.org/trunk5/en/kde-workspace/plasma-deskto"&gt;https://docs.kde.org/trunk5/en/kde-workspace/plasma-deskto&lt;/a&gt;
p/activities-interface.html)&lt;/em&gt;, which is a feature implemented in KDE
that allows you to use virtual spaces literally for different activities
by having settings that are specific to each activity (like launching
one application or keeping widgets in determined positions), plus the
ability to use multiple virtual desktops inside each &lt;em&gt;activity&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;They integrate with applications that are specifically designed to
take advantage of the desktop environment in question by adapting
colors and themes perfectly to match the general look of the system and
arranging settings in a common location for all applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="why-use-a-window-manager-like-i3"&gt;Why use a window manager like i3?&lt;/h3&gt;
&lt;p&gt;Even though desktop environments are user-friendly and include many
goodies, they do have several disadvantages that make window managers
great alternatives.&lt;/p&gt;
&lt;h4 id="less-bloated"&gt;Less bloated&lt;/h4&gt;
&lt;p&gt;Desktop environments are often bloated with many applications
that you may not need but that are nevertheless occupying system
resources as they have many dependencies. For example, if you use
&lt;a href="https://www.thunderbird.net"&gt;Thunderbird&lt;/a&gt; for managing your emails and
your desktop environment is GNOME, try to remove Evolution, the default
application for emails that comes with it&amp;hellip; It&amp;rsquo;s still relatively easy
to disable its processes, but you sometimes have to configure files
manually to satisfy your need for removing clutter.&lt;/p&gt;
&lt;h4 id="lower-memory-and-cpu-consumption"&gt;Lower memory and CPU consumption&lt;/h4&gt;
&lt;p&gt;Other than coming with a set of default applications (more disk space),
the whole environment occupies a lot more memory to run smoothly. On
my modest laptop, I was still experiencing speed issues because the
system was occupying too much RAM when opening heavy applications (lots
of tabs when web browsing, photo editing, loading IDEs like PyCharm
for programming, etc.) and I had 4 GB of RAM installed at the time.
KDE requires at least 600 MB, same is true for GNOME in most recent
versions (but used to be over 1 GB not very long ago, just like for
KDE). Windows, for example, requires more than 1.5 GB of RAM to operate
with basic features. &lt;strong&gt;&lt;code&gt;i3&lt;/code&gt;&lt;/strong&gt; needs less than &lt;strong&gt;~15 MB&lt;/strong&gt; with as many
windows and spaces open as you would ever want. Since there are much
less processes to take care of, CPU consumption is also lower, which is
more noticeable on older machines.&lt;/p&gt;
&lt;h4 id="keyboard-efficiency"&gt;Keyboard efficiency&lt;/h4&gt;
&lt;p&gt;There are obviously ways to go about using keyboard shortcuts for pretty
much anything you want with desktop environments. However, window
managers and especially &lt;code&gt;i3&lt;/code&gt; make this a must, as there is really no
reason to use a mouse for moving windows.&lt;/p&gt;
&lt;p&gt;First of all, windows are &amp;ldquo;stuck&amp;rdquo; in place and never overlap by default.
You can&amp;rsquo;t move them around with a mouse unless they are &amp;ldquo;floating&amp;rdquo;
windows, which means you can separate them to move them around freely
like in most desktop environments. The whole point of using a window
manager like &lt;code&gt;i3&lt;/code&gt; is to maximize screen space, so every window opens
in full screen. Honestly, the only time I ever found the need to use a
floating window with &lt;code&gt;i3&lt;/code&gt; is when using the operating system Windows in
a virtual environment when connecting through RDP with Google Chrome on
Google Cloud Platform (that is, not very often). Other than that, I find
it very practical to split the screen with no overlapping windows, even
with my small 15 inches screen.&lt;/p&gt;
&lt;p&gt;Because windows are managed the way they are in &lt;code&gt;i3&lt;/code&gt;, getting split when
multiple windows are in the same workspace and never overlapping, it
nurtures the good habit of dividing workspaces appropriately to maximize
the space that each application occupies, which is done with keyboard
shortcuts. With (customizable) shortcuts, you can split windows,
move them to other workspaces, change workspaces, open applications
automatically in specific workspaces, etc. Using exclusively the
keyboard to manage the windows becomes a huge time saver, and even more
so when considering the very small footprint required to use the window
manager, making for a lightning fast experience.&lt;/p&gt;
&lt;h4 id="extremely-fast-customization"&gt;Extremely fast customization&lt;/h4&gt;
&lt;p&gt;Once you get past the initial lack of visual appeal and weird way to
move around, you can actually customize many aspects of &lt;code&gt;i3&lt;/code&gt;, including
fonts, mouse bindings, window border decorations, colors, automatic
execution of apps when initiating a session, etc.&lt;/p&gt;
&lt;p&gt;Why is it so fast to customize? Because the configuration is all
contained in one simple text file and you can make a change and reload
the window manager on the fly to see your changes being reflected.
Setting the number of workspaces is as easy as adding or removing a line
(well, as a bare minimum, otherwise it&amp;rsquo;s useful to also bind certain
keyboard shortcuts to move to those newly created workspaces). Setting a
wallpaper is one line, setting a screen lock is another one. Quick and
efficient.&lt;/p&gt;
&lt;h4 id="easy-to-switch-layouts-and-focus"&gt;Easy to switch layouts and focus&lt;/h4&gt;
&lt;p&gt;I actually enjoy the default layout quite a lot, in which there exists
no overlap between windows (they split as needed) and all windows are
visible at the same time in each workspace. If you need to make more
space for a window, you can either &amp;ldquo;focus&amp;rdquo; on the current window, which
will make it full screen and cover every pixel of the screen, or you
can move particular windows to other workspaces. This last option will
automatically resize windows so that they occupy the maximum space
available.&lt;/p&gt;
&lt;p&gt;Besides the default layout, you can also &lt;em&gt;stack&lt;/em&gt; and &lt;em&gt;tab&lt;/em&gt; windows,
depending on how you like to work (it is well worth having a look at the
&lt;a href="https://i3wm.org/docs/userguide.html"&gt;i3 user&amp;rsquo;s guide&lt;/a&gt; to see the many
other features available). Personally, I prefer seeing everything at
once and dividing my work into many workspaces. If you are curious, here
is how I currently have my workspaces set up:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Programming&lt;/strong&gt; — Exclusively for programming purposes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terminals&lt;/strong&gt; — For working in the terminal, opening another
instance of a terminal text editor, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web&lt;/strong&gt; — Just for Internet. Always full screen unless I open
some website that requires to open new windows (like banking), which
conveniently split the windows. The web browser automatically launches
in the background when opening a session in &lt;code&gt;i3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multimedia&lt;/strong&gt; — Editing photos or watching videos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Music&lt;/strong&gt; — &lt;a href="https://github.com/arybczak/ncmpcpp"&gt;ncmpcpp&lt;/a&gt;
(terminal music player) automatically launches in the background when
opening a session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Experiments&lt;/strong&gt; — I use this workspace when trying to understand
some programming concepts with an interactive IPython console or for
breaking the system in one way or another because I don&amp;rsquo;t know what I&amp;rsquo;m
doing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix&lt;/strong&gt; — After realizing that sometimes experiments need to be
cleaned up a bit, I now use this workspace exclusively to run tools
in the background, such as &lt;a href="https://www.bleachbit.org"&gt;BleachBit&lt;/a&gt; for
cleaning files or &lt;a href="https://nicolargo.github.io/glances/"&gt;glances&lt;/a&gt;
(similar to the &lt;code&gt;top&lt;/code&gt; command) for watching how the system is doing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Words&lt;/strong&gt; — I use that workspace for reading e-books
or for writing, either in &lt;a href="https://neovim.io/"&gt;Neovim&lt;/a&gt; or
&lt;a href="https://www.libreoffice.org/"&gt;LibreOffice&lt;/a&gt; depending on the task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt; — This workspace is reserved for anything that I
might want to have access to once in a while, like a calendar or
&lt;a href="https://keepassxc.org/"&gt;KeepassXC&lt;/a&gt; for accessing a password database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sg&lt;/strong&gt; — I mainly use that workspace to update this very
website, but I also use it for setting up local web servers for
making personal projects in &lt;a href="http://flask.pocoo.org/"&gt;Flask&lt;/a&gt; and
&lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; or even just a temporary server
with &lt;code&gt;python -m http.server&lt;/code&gt; to share files with other devices on the
local network.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Only workspaces that contain windows will appear on the screen at any
given time and are automatically created when you access and use them.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-does-it-look-like-in-action"&gt;What does it look like in action?&lt;/h2&gt;
&lt;p&gt;Here is a screenshot* of the workspace where this article is being
written (click to open):&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.sglavoie.com/images/posts/0005_using-i3-as-window-manager-for-increased-productivity/i3-example.png"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0005_using-i3-as-window-manager-for-increased-productivity/i3-example.png" alt="i3-example" class="max-size-img-post"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;* &lt;sub&gt;In this particular scenario, the splits on the right side are
intentionally very small as I do not need to read the output, but need
quick access to a terminal to enter commands. Moving back and forth
between windows is a breeze, so that&amp;rsquo;s how I currently handle the
situation.&lt;/sub&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: With &lt;code&gt;Neovim&lt;/code&gt;, &lt;a href="https://www.sglavoie.com/posts/using-embedded-terminals-inside-neovim/"&gt;there is an embedded
terminal&lt;/a&gt;,
which makes things very easy to handle by avoiding splits altogether.
In that case, it is also possible to divide a workflow with tabs inside
&lt;code&gt;Neovim&lt;/code&gt; instead of physically splitting windows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alternatively&lt;/strong&gt;, instead of splitting windows, a more convenient
solution on smaller screens might be to use &lt;strong&gt;stacking&lt;/strong&gt; windows with
the shortcut &lt;code&gt;$mod + s&lt;/code&gt; in each workspace or, to provide that behavior
by default, the following can be added to i3&amp;rsquo;s configuration file
(defaults in &lt;code&gt;~/.config/i3/config&lt;/code&gt;):&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set default container layout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace_layout stacking&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I am not trying to convince myself anymore: I really prefer this
workflow! If you are looking for a
change and would like to maximize both your productivity and your system
resources, &lt;code&gt;i3&lt;/code&gt; is a great window manager to consider. It doesn&amp;rsquo;t seem
very intuitive at first, but I swear it quickly becomes easy to use and
you also end up learning more about how Linux works and set things up
manually (just once!) to your likings. For example, I use Rofi instead
of dmenu to have a window pop-up to select applications or switch to
any window on any workspace&amp;hellip; which was only one line to modify in the
configuration file.&lt;/p&gt;
&lt;p&gt;I have found the following screencasts on YouTube to be quite useful in
learning how to configure &lt;code&gt;i3&lt;/code&gt; (apart from the official documentation
which is very comprehensive):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=j1I63wGcvU4"&gt;i3wm: Jump Start (1/3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8-S0cWnLBKg"&gt;i3wm: Configuration (2/3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ARKIwOlazKI"&gt;i3wm: How To &amp;ldquo;Rice&amp;rdquo; Your Desktop (3/3)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Tue, 08 Jan 2019 11:12:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/using-i3-as-a-window-manager-for-increased-productivity/</guid><category>workflow</category><category>i3</category><category>linux</category><category>productivity</category><category>window-manager</category></item><item><title>Using Vimwiki for Note-Taking</title><link>https://www.sglavoie.com/posts/using-vimwiki-for-note-taking/</link><description>This short article explains why Vimwiki felt like the right solution to organize my notes.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="what-is-vimwiki"&gt;What is Vimwiki?&lt;/h2&gt;
&lt;p&gt;From the &lt;a href="https://vimwiki.github.io/"&gt;official website&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Vimwiki is a personal wiki for Vim – interlinked, plain text files
written in a markup language.&lt;/p&gt;

&lt;/blockquote&gt;&lt;p&gt;As this relies upon Vim&amp;rsquo;s power to write content in a simple format,
this quickly became a viable option to organize more and more notes!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-were-the-other-options-considered"&gt;What were the other options considered?&lt;/h2&gt;
&lt;p&gt;I have used various approaches in the past for different purposes and
reasons, including &lt;em&gt;Evernote&lt;/em&gt;, &lt;em&gt;Simplenote&lt;/em&gt;, &lt;em&gt;Microsoft OneNote&lt;/em&gt;,
&lt;em&gt;Google Docs&lt;/em&gt;, &lt;em&gt;Boostnote&lt;/em&gt;, &lt;em&gt;Freeplane&lt;/em&gt;/&lt;em&gt;FreeMind&lt;/em&gt; (mind-mapping),
&lt;em&gt;Workflowy&lt;/em&gt; and even plain &lt;code&gt;.txt&lt;/code&gt; files. Each has its pros and cons, but
I then decided to limit my options based on the following self-imposed
requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has to be &lt;strong&gt;available on Linux&lt;/strong&gt;. Right from the start, this would
disqualify many applications such as &lt;em&gt;Notational Velocity&lt;/em&gt;, &lt;em&gt;Bear&lt;/em&gt;,
&lt;em&gt;Quiver&lt;/em&gt;, &lt;em&gt;Paper&lt;/em&gt;, &lt;em&gt;Ulysses&lt;/em&gt; and &lt;em&gt;Squid&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;It has to be &lt;strong&gt;open source&lt;/strong&gt; as this is a philosophy that I strongly
embrace. Plus, having the ability to freely modify it and contribute to
the project is an important additional bonus. Adiós &lt;em&gt;Evernote&lt;/em&gt;, &lt;em&gt;Google
Docs&lt;/em&gt;, &lt;em&gt;Dropbox Paper&lt;/em&gt;, &lt;em&gt;Microsoft OneNote&lt;/em&gt;, &lt;em&gt;Workflowy&lt;/em&gt; and &lt;em&gt;Quip&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;It has to be &lt;strong&gt;fast to use&lt;/strong&gt; and it has to make it possible
to &lt;strong&gt;express oneself in more than one way&lt;/strong&gt;. Say goodbye to
&lt;em&gt;&lt;a href="https://www.freeplane.org"&gt;Freeplane&lt;/a&gt;&lt;/em&gt; (otherwise great for
general brainstorming!) and &lt;em&gt;FreeMind&lt;/em&gt; (which has been abandoned
by its own developers): both are relatively slow on an old machine
because they use Java and it is quite a stretch to use them for
something other than mind maps. In that same category would disappear
&lt;em&gt;&lt;a href="https://www.draw.io/"&gt;draw.io&lt;/a&gt;&lt;/em&gt;, which is fantastic for making
flowcharts and diagrams!&lt;/li&gt;
&lt;li&gt;It has to be versatile enough to handle features such as &lt;strong&gt;including
images, links and attached files&lt;/strong&gt; and have a way to perform &lt;strong&gt;search
and replace&lt;/strong&gt;. Ciao &lt;em&gt;&lt;a href="https://simplenote.com/"&gt;Simplenote&lt;/a&gt;&lt;/em&gt; which
is, well, simple. Even though it doesn&amp;rsquo;t fit the bill in this case,
it remains a great option as it can synchronize your notes with many
different devices (iOS, macOS, Android, Windows and Linux). It has
a feature that allows you to move a slider which acts as a timeline
and shows you a different version of your note since its creation
with the actual date and time down to the minute for each &lt;em&gt;restore
point&lt;/em&gt;. You can also use tags and Markdown and it has options to
share and collaborate with others. Highly recommended!&lt;/li&gt;
&lt;li&gt;It should have a hefty user base to back it up. This would exclude
text editor plugins such as &lt;code&gt;atom-notes&lt;/code&gt; for &lt;em&gt;Atom&lt;/em&gt; or &lt;code&gt;VSNotes&lt;/code&gt; for &lt;em&gt;VS
Code&lt;/em&gt;, which also lack features for accomplishing all of the above.&lt;/li&gt;
&lt;li&gt;On top of everything else, it has to be &lt;strong&gt;available offline&lt;/strong&gt;, in a
&lt;strong&gt;portable and readable format&lt;/strong&gt;. Now, after discarding most options, we
are left with &lt;em&gt;Boostnote&lt;/em&gt; which uses CoffeeScript-Object-Notation and
&lt;em&gt;Vimwiki&lt;/em&gt; which has its own Wiki syntax that&amp;rsquo;s similar in many ways to
Markdown*. Those are the strongest contenders that I could think of but
of course, if you would like to share your recommendations, please do so
in the comments below!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;* &lt;sub&gt;I somehow discredited
&lt;strong&gt;&lt;a href="https://www.gnu.org/software/emacs"&gt;Emacs&lt;/a&gt;&lt;/strong&gt; simply because I started
to learn Vim first and since I&amp;rsquo;m still far from understanding all of
its features, I had to postpone the discovery of Emacs.&lt;/sub&gt;&lt;/p&gt;
&lt;h3 id="one-feature-rich-alternative-to-vimwiki"&gt;One feature-rich alternative to Vimwiki&lt;/h3&gt;
&lt;p&gt;After settling down on Vimwiki, I later found out about &lt;a
href="https://joplin.cozic.net"&gt;Joplin&lt;/a&gt;, which comes with many great
features and characteristics such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Free &amp;amp; Open Source&lt;/li&gt;
&lt;li&gt;Manages notes and todo lists&lt;/li&gt;
&lt;li&gt;Search across all notes/todos&lt;/li&gt;
&lt;li&gt;Import/Export from/to various formats, including Markdown and even
imports from Evernote&lt;/li&gt;
&lt;li&gt;Support for attachments&lt;/li&gt;
&lt;li&gt;Tags&lt;/li&gt;
&lt;li&gt;Synchronization with multiple providers such as Dropbox, Nextcloud,
OneDrive and even your own private cloud&lt;/li&gt;
&lt;li&gt;Available for Windows, Linux, macOS, Android and iOS&lt;/li&gt;
&lt;li&gt;Web Clipper integration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This application left me a great first impression to say the least and I
will make sure to stay up to date on its active development, which you
can &lt;a href="https://github.com/laurent22/joplin"&gt;follow on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="why-did-vimwiki-win-in-the-end"&gt;Why did Vimwiki win in the end?&lt;/h2&gt;
&lt;h3 id="in-short"&gt;In short&lt;/h3&gt;
&lt;blockquote&gt;
 &lt;p&gt;The modal nature of Vim makes it very hard to enjoy any other text
editor once you get used to it. The learning curve is quite considerable
and nearly infinite, but this is also why I think it is worth investing
more time to master it as it has proven to be an everlasting piece of
trusty software.&lt;/p&gt;

&lt;/blockquote&gt;&lt;blockquote&gt;
 &lt;p&gt;There is a feature in Boostnote to set the Editor Keymap to &lt;code&gt;vim&lt;/code&gt; so
that you can edit text in a very similar way, but then you miss out on
Vim&amp;rsquo;s &lt;code&gt;Command mode&lt;/code&gt; which adds tremendous extensibility and the ability
to create any mappings you wish on the fly.&lt;/p&gt;

&lt;/blockquote&gt;&lt;hr&gt;
&lt;h4 id="expanded-edition"&gt;Expanded edition&lt;/h4&gt;
&lt;p&gt;Boostnote comes with nice features such as Tags, the ability to set
multiple storage locations for notes, a &lt;code&gt;Preferences&lt;/code&gt; panel to adjust
many options, a feature to add code snippets, real-time preview of
Markdown being edited, etc. Make no mistake: this is a great program.
But there are many Vim features that are hard or impossible to replicate
and everything that you can do in Boostnote can be done in Vim also
(ctags, emmet syntax, plugins for Markdown, etc.).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For starters, the terminal integration is obviously unmatched. In
Vim, &lt;code&gt;CTRL+Z&lt;/code&gt; will &lt;em&gt;stop&lt;/em&gt; Vim and gives you access to the terminal to
do whatever you want to do. From that point, you can simply switch back
to Vim by issuing the command &lt;code&gt;fg&lt;/code&gt;. The terminal integration goes even
further: you can have full access to the terminal inside Vim buffers
since Vim 8.1 and that is a feature that has been available in Neovim
for even longer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Even though &lt;code&gt;vim&lt;/code&gt; mode can be enabled to edit text, you have to use
the mouse to get many tasks done and the editing window can loose the
focus. On the other hand, Vim is just one single window by default that
you can split however you want, including adding tabs if your heart
tells you to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You have to open your notes with Boostnote if you want them to be
fully readable out of the box. Vimwiki does almost no processing with
the content of the files so that it is very easy to open them with any
other text editor. On a related note, it is much easier to export many
notes at the same time with Vimwiki. Boostnote does have a few options
to export individual notes (&lt;code&gt;.md&lt;/code&gt;, &lt;code&gt;.txt&lt;/code&gt;, &lt;code&gt;.html&lt;/code&gt; and print), but it is
not as user-friendly with many notes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vimwiki makes it easy to link notes together and navigate between
them, even within subfolders: highlight text, press &lt;code&gt;Enter&lt;/code&gt; to create a
link and open a new note, write your note and press &lt;code&gt;Backspace&lt;/code&gt; to go
back to where you created the link. That&amp;rsquo;s quick and easy!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In the end, it is a matter of taste as those programs are indeed very
distinct. Boostnote has a polished interface, is much easier to use and
has many settings easily changed. Vim/Neovim, on the contrary, requires
a lot of initial effort both to understand and to set up according
to your needs, but it does deliver a good dose of productivity&amp;hellip;
Eventually!&lt;/p&gt;
&lt;p&gt;An honorable mention goes to Simplenote, which is a joy to use with
mobile devices and allows for quick synchronization between different
devices across all the supported platforms.&lt;/p&gt;
&lt;p&gt;Where things really get in favor of Vim, you could mention the extensive
help system (command &lt;code&gt;:help&lt;/code&gt;), the use of macros, words and lines
completion, the dot (&lt;code&gt;.&lt;/code&gt;) command, the many registers at your disposal
for different tasks, the impressive amount of plugins available, the
ways in which you can configure mappings, functions&amp;hellip; But that&amp;rsquo;s for a
whole new story!&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Mon, 03 Dec 2018 13:26:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/using-vimwiki-for-note-taking/</guid><category>workflow</category><category>neovim</category><category>note-taking</category><category>productivity</category><category>vim</category><category>vimwiki</category></item><item><title>Tmux Integration with Vim/Neovim — First Steps</title><link>https://www.sglavoie.com/posts/tmux-integration-with-vim-neovim-first-steps/</link><description>This is a short introduction that shows a possible workflow with tmux and Vim/Neovim.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="tmux-integration"&gt;tmux integration&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/tmux/tmux"&gt;tmux&lt;/a&gt; offers many advantages in the
context of remote access to another machine, but it also shines on a
local setup! Here is how I currently like to set it up.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="configuration-file"&gt;Configuration file&lt;/h3&gt;
&lt;div class="code-block code-block--titled"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__title"&gt;~/.tmux.conf&lt;/span&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight" title="~/.tmux.conf"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# split panes using | and -&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;bind&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; split-window -h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;bind&lt;/span&gt; - split-window -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;unbind &lt;span class="s1"&gt;&amp;#39;&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;unbind %
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# switch panes using Alt-arrow without prefix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;bind&lt;/span&gt; -n M-Left &lt;span class="k"&gt;select&lt;/span&gt;-pane -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;bind&lt;/span&gt; -n M-Right &lt;span class="k"&gt;select&lt;/span&gt;-pane -R
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;bind&lt;/span&gt; -n M-Up &lt;span class="k"&gt;select&lt;/span&gt;-pane -U
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;bind&lt;/span&gt; -n M-Down &lt;span class="k"&gt;select&lt;/span&gt;-pane -D
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Enable mouse mode (tmux 2.1 and above)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -g mouse on
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;source-file &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.tmux-themepack/blue.tmuxtheme&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is all very self-explanatory. &lt;a href="https://github.com/jimeh/tmux-themepack"&gt;Many themes can be found
here&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="automating-the-launch-of-a-default-session"&gt;Automating the launch of a default session&lt;/h3&gt;
&lt;p&gt;This is a small Bash script that I like to have for automatic set up
of a development environment with tmux and Vim/Neovim. As my current
workflow, I enjoy the following:&lt;/p&gt;
&lt;div class="code-block code-block--titled"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__title"&gt;~/.tmux_default_session.sh&lt;/span&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight" title="~/.tmux_default_session.sh"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# var for session name (to avoid repeated occurences)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;sn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux new-session -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$sn&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create 3 windows&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux new-window -t &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$sn&lt;/span&gt;&lt;span class="s2"&gt;:0&amp;#34;&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;nvim&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux new-window -t &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$sn&lt;/span&gt;&lt;span class="s2"&gt;:9&amp;#34;&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;python&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux new-window -t &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$sn&lt;/span&gt;&lt;span class="s2"&gt;:8&amp;#34;&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;terminal&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Split terminal window vertically, then split the right pane&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# horizontally, then switch to the left pane (identified by `FOCUS IS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# HERE`).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# _________________&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# |&amp;gt;_ |&amp;gt;_ |&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# | | |&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# | FOCUS |-------|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# | IS |&amp;gt;_ |&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# | HERE | |&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# -----------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux split-window -h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux split-window -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux &lt;span class="k"&gt;select&lt;/span&gt;-pane -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set up Neovim ready to open files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux send-keys -t &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$sn&lt;/span&gt;&lt;span class="s2"&gt;:0&amp;#34;&lt;/span&gt; C-z &lt;span class="s1"&gt;&amp;#39;nvim .&amp;#39;&lt;/span&gt; Enter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set up alias for IPython and clear the screen when entering IPython&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux send-keys -t &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$sn&lt;/span&gt;&lt;span class="s2"&gt;:9&amp;#34;&lt;/span&gt; C-z &lt;span class="s1"&gt;&amp;#39;ipython&amp;#39;&lt;/span&gt; Enter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux send-keys -t &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$sn&lt;/span&gt;&lt;span class="s2"&gt;:9&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;clear&amp;#39;&lt;/span&gt; Enter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Select window #0 and attach to the session&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux &lt;span class="k"&gt;select&lt;/span&gt;-window -t &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$sn&lt;/span&gt;&lt;span class="s2"&gt;:0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tmux -2 attach-session -t &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$sn&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3 id="aliases-to-make-use-of"&gt;Aliases to make use of&lt;/h3&gt;
&lt;p&gt;I add the following aliases in &lt;code&gt;~/.bash_aliases&lt;/code&gt; to automate a chunk of
the workflow:&lt;/p&gt;
&lt;div class="code-block code-block--titled"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__title"&gt;~/.bash_aliases&lt;/span&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight" title="~/.bash_aliases"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# This will launch tmux with the desired configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bash ~/.tmux_default_session.sh&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# This will kill the tmux server if the need arises. By detaching from&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# the tmux session, you simply run this command and can reattach easily&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# to the default session configuration on another project.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;kdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pkill tmux&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# If the session has been detached but the tmux server is still running,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# I use the following alias to quickly reattach to the default session&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# named `dev` in that example.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;adev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tmux attach-session -t dev&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3 id="tmux-demo"&gt;tmux demo&lt;/h3&gt;
&lt;p&gt;(click to open image)
&lt;a href="https://www.sglavoie.com/images/posts/0001_tmux-integration-with-vim/tmux-demo.gif"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0001_tmux-integration-with-vim/tmux-demo.gif" alt="Animated tmux and Vim integration demo" class="max-size-img-post" width="1366" height="768" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The content of this post can be found on
&lt;a href="https://github.com/sglavoie/better-vim-experience#tmux-integration"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Mon, 19 Nov 2018 20:31:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/tmux-integration-with-vim-neovim-first-steps/</guid><category>workflow</category><category>neovim</category><category>terminal</category><category>tmux</category><category>vim</category></item></channel></rss>