<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohammad Rafiq</title>
    <description>The latest articles on DEV Community by Mohammad Rafiq (@bwfiq).</description>
    <link>https://dev.to/bwfiq</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2823106%2F00d8fb07-b641-4e8a-8428-64ef8b827f40.jpeg</url>
      <title>DEV Community: Mohammad Rafiq</title>
      <link>https://dev.to/bwfiq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bwfiq"/>
    <language>en</language>
    <item>
      <title>TIL - How to Fix Flaky macOS Screen Capture on OBS</title>
      <dc:creator>Mohammad Rafiq</dc:creator>
      <pubDate>Sat, 25 Oct 2025 02:35:48 +0000</pubDate>
      <link>https://dev.to/bwfiq/til-how-to-fix-flaky-macos-screen-capture-on-obs-46dg</link>
      <guid>https://dev.to/bwfiq/til-how-to-fix-flaky-macos-screen-capture-on-obs-46dg</guid>
      <description>&lt;p&gt;On macOS, when switching scenes in OBS, if you move from a scene with a Display Capture source to one without it, macOS stops the screen capture session. When you switch back, the display stays frozen until you re-enable it in the source properties.&lt;/p&gt;

&lt;p&gt;The workaround is to include the Display Capture source in every scene, even the ones that don’t use it. Just hide it by clicking the eye icon or placing it below another layer. This keeps the capture session active and prevents interruptions when switching scenes.&lt;/p&gt;

&lt;p&gt;You may still encounter issues where the macOS Screen Capture source still keeps freezing and requires manual restarting in the properties of the source. The best (for now) fix for this is to use &lt;a href="https://github.com/yayuanli/OBS_Restart_Capture_Stuck_Source" rel="noopener noreferrer"&gt;this script&lt;/a&gt; which will monitor the source and restart it if it's frozen.&lt;/p&gt;

&lt;p&gt;I have a Nix Flake setup &lt;a href="https://github.com/rrvsh/OBS_Restart_Capture_Stuck_Source" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you prefer that (or have to like me).&lt;/p&gt;

</description>
      <category>macos</category>
      <category>obs</category>
    </item>
    <item>
      <title>git init ~/repos/cathedral</title>
      <dc:creator>Mohammad Rafiq</dc:creator>
      <pubDate>Fri, 03 Oct 2025 14:49:47 +0000</pubDate>
      <link>https://dev.to/bwfiq/git-init-reposcathedral-4efe</link>
      <guid>https://dev.to/bwfiq/git-init-reposcathedral-4efe</guid>
      <description>&lt;p&gt;okay, ive been using NixOS with this &lt;a href="https://github.com/rrvsh/pantheon" rel="noopener noreferrer"&gt;flake&lt;/a&gt; for a while but I've kind of let it lie sallow for a while out of a kind of dissatisfaction with how I've done things. I'd love a proper rewrite with all the experience I have gained doing this.&lt;/p&gt;

&lt;p&gt;i have moped around about doing this for months on end but it is time i cannot take not being able to deploy random shit and my servers are all down anyway IT IS TIME to go.&lt;/p&gt;

&lt;p&gt;i broke down the issue and the number one thing that matters to me is having the product I can see and play with. To just "get there", I decided let's just start with the easiest and the edge node, the raspberry pi that will be my reverse proxy. The first step would simply be flashing a microSD card with a NixOS image with my SSH keys on there.&lt;/p&gt;

&lt;p&gt;I almost instantly got ganked by my job (new company btw, finally SOFTWARE ENGINEERING) on the day I chose to do this small task, but managed to get er' done!&lt;/p&gt;

&lt;h2&gt;
  
  
  With Some Caveats:
&lt;/h2&gt;

&lt;p&gt;It took 6 hours. I ran into issues with git when I was working off of two branches on pantheon. I decided on a whole new &lt;a href="https://github.com/rrvsh/cathedral" rel="noopener noreferrer"&gt;name change&lt;/a&gt; (obviously very inspired by &lt;a href="http://www.catb.org/esr/faqs/hacker-howto.html" rel="noopener noreferrer"&gt;CatB&lt;/a&gt;). I got distracted by a myriad other tasks, mostly also nix-related. &lt;/p&gt;

&lt;h2&gt;
  
  
  Anyway,
&lt;/h2&gt;

&lt;p&gt;Next step is going to be getting my very static website back up on my domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;           services = {
             ...
             nginx.enable = true;
&lt;span class="gd"&gt;-            nginx.virtualHosts."_".root = "/var/www/veil";
&lt;/span&gt;&lt;span class="gi"&gt;+            nginx.virtualHosts."_".root = ../www/rrv.sh;
&lt;/span&gt;           };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I'm doing here is essentially adding &lt;a href="https://github.com/rrvsh/cathedral/tree/4052c606529ed90b3159977e03f2affe4f048173/www/rrv.sh" rel="noopener noreferrer"&gt;the static files&lt;/a&gt; into the project and using Nix's paths to add it into the Nginx configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detour:
&lt;/h2&gt;

&lt;p&gt;My ACME validation (for SSL) uses DNS-01, for which I need a Cloudflare API key, which I obviously don't want in plaintext in my repo, so it is time to add declarative secrets management with &lt;a href="https://github.com/Mic92/sops-nix" rel="noopener noreferrer"&gt;sops-nix&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/docs/runbook.md b/docs/runbook.md
&lt;/span&gt;&lt;span class="gi"&gt;+ssh-to-age -private-key -i ~/.ssh/id_ed25519 &amp;gt; ~/.config/sops/age/keys.txt
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/flake.nix b/flake.nix
&lt;/span&gt;   inputs = {
&lt;span class="gi"&gt;+    sops-nix.url = "github:Mic92/sops-nix";
+    sops-nix.inputs.nixpkgs.follows = "nixpkgs";
&lt;/span&gt;   };
 }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/nix/users.yaml b/nix/users.yaml
&lt;/span&gt;&lt;span class="gi"&gt;+rafiq:
+    password: ENC[AES256_GCM,data:8KAfatz+YSaNozd5VGo=,iv:LNRxt47iBKSWzMZuBHSxv/qDZ2h6JiTIPps7OK/o7uU=,tag:oiSfLyRVswb/wxSTE69QMA==,type:str]
+    hashedPassword: ENC[AES256_GCM,data:NogYQ3kR1TseC79HIXARrXhIncCnvxzf9zMF2QrUyTmojTffPXRGtMdjNpfMEFj5dkKfZujBL/QTIpPFFTm1py7Dreg5/9VSKQ==,iv:IwfZsrsJbLYG1ELte6aBHUtff6hIQu9rHT5tSvILIGQ=,tag:oav3paDcUY+cl4FJlZa90A==,type:str]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/nix/veil.nix b/nix/veil.nix
&lt;/span&gt;   flake.nixosConfigurations.veil = lib.nixosSystem {
     modules = [
       "${inputs.nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix"
&lt;span class="gi"&gt;+      inputs.sops-nix.nixosModules.sops
&lt;/span&gt;       (
         {
           # Use the nix-community binary cache so we don't have to compile sops for aarch64-linux ourselves
&lt;span class="gi"&gt;+          nix.settings = {
+            substituters = [ "https://nix-community.cachix.org" ];
+            trusted-substituters = [ "https://nix-community.cachix.org" ];
+            trusted-public-keys = [ "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" ];
+          };
&lt;/span&gt;           users = {
&lt;span class="gi"&gt;+            mutableUsers = false;
&lt;/span&gt;             users.rafiq = {
&lt;span class="gi"&gt;+              hashedPasswordFile = config.sops.secrets."rafiq/hashedPassword".path;
&lt;/span&gt;             };
           };
&lt;span class="gi"&gt;+          sops = {
+            age.sshKeyPaths = [ "/home/rafiq/.ssh/id_ed25519" ];
+            secrets."rafiq/hashedPassword" = {
+              neededForUsers = true;
+              sopsFile = ./users.yaml;
+            };
+          };
&lt;/span&gt;         }
       )
     ];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm a bit out of practice since I first touched sops in &lt;a href="https://github.com/rrvsh/pantheon/commit/26ba53fee38e79ccdc438d37260f09f535b91e11" rel="noopener noreferrer"&gt;March&lt;/a&gt;, so I was a little confused why my sops config was working (which was the goal) but the &lt;code&gt;hashedPasswordFile&lt;/code&gt; option wasn't kicking in. Luckily I figured out I just forgot to set &lt;code&gt;mutableUsers = false&lt;/code&gt;. Now we have our password in our git repo!&lt;/p&gt;

&lt;h2&gt;
  
  
  Lastly:
&lt;/h2&gt;

&lt;p&gt;Now we have declarative secret managment, I'll add the cloudflare key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/nix/keys.yaml b/nix/keys.yaml
&lt;/span&gt;&lt;span class="gi"&gt;+keys:
+    cloudflare: ENC[AES256_GCM,data:p2IISOuU/ShoifW5OFY/6Bi6PI0iIiQoBfnV512f2z84U9QS/KEhzA==,iv:5AkwtNAK8mD2DbvXCtTeNeIrpF/GIsSyOYxy8G4Jsqo=,tag:u2xJcRBR5WTMWdzupx4tbQ==,type:str]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's use it in sops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/nix/veil.nix b/nix/veil.nix
&lt;/span&gt;           sops = {
&lt;span class="gi"&gt;+            secrets."keys/cloudflare".sopsFile = ./keys.yaml;
&lt;/span&gt;           };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and finally use it for our DNS-01 validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/nix/veil.nix b/nix/veil.nix
&lt;/span&gt;           services = {
&lt;span class="gd"&gt;-            nginx.virtualHosts."_".root = ../www/rrv.sh;
&lt;/span&gt;&lt;span class="gi"&gt;+            nginx.virtualHosts."rrv.sh" = {
+              addSSL = true;
+              useACMEHost = "rrv.sh";
+              acmeRoot = null; # needed for DNS validation
+              locations."/".root = ../www/rrv.sh;
+            };
&lt;/span&gt;           };
           users = {
&lt;span class="gi"&gt;+            users.nginx.extraGroups = [ "acme" ];
&lt;/span&gt;           };
           security = {
&lt;span class="gi"&gt;+            acme = {
+              acceptTerms = true;
+              defaults = {
+                email = "rafiq@rrv.sh";
+                dnsProvider = "cloudflare";
+                credentialFiles."CLOUDFLARE_DNS_API_TOKEN_FILE" = config.sops.secrets."keys/cloudflare".path;
+              };
+              certs."rrv.sh".extraDomainNames = [ "*.rrv.sh" ];
+            };
&lt;/span&gt;           };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and that's it! A quick ssh and &lt;code&gt;nixos-rebuild switch --sudo --flake .&lt;/code&gt; later, &lt;code&gt;veil&lt;/code&gt; is up and more importantly, so is &lt;a href="https://rrv.sh" rel="noopener noreferrer"&gt;my website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="https://github.com/rrvsh/cathedral/tree/4052c606529ed90b3159977e03f2affe4f048173" rel="noopener noreferrer"&gt;repo at this point&lt;/a&gt;. I'm trying to document any other interesting things I come up with in the process, so hopefully you see more blog posts soon. Side note: still pretty crazy that i can go from a dead Raspberry Pi to a live, SSL-secured website in a single rebuild — if that doesn't make you wanna try nix, idk what does.&lt;/p&gt;

&lt;p&gt;Next step is getting my DnD wiki back up, which is unfortunately going to involve me learning how to declare docker compose with nix. see ya next time&lt;/p&gt;

</description>
      <category>ssl</category>
      <category>raspberrypi</category>
      <category>nixos</category>
    </item>
    <item>
      <title>mise en place</title>
      <dc:creator>Mohammad Rafiq</dc:creator>
      <pubDate>Thu, 18 Sep 2025 10:32:35 +0000</pubDate>
      <link>https://dev.to/bwfiq/mise-en-place-9g4</link>
      <guid>https://dev.to/bwfiq/mise-en-place-9g4</guid>
      <description>&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;I have an issue with premature optimisation. Not with my software projects, but in my life. I often, when trying to implement some kind of routine change or enhancement to my workflow, overdo it and try to hit the nail on the head the first time. This leads to the classic analysis by paralysis, where I'm stuck perfecting a system for a given task and end up never actually putting into practice, or worse,  I invest so much time into perfecting it and it ends up not working for me.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Perfect is the enemy of good enough.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The New Routine
&lt;/h2&gt;

&lt;p&gt;Today, I put the following simple rules into practice, and will be following these to the tee to the best of my ability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write down every single task you want to accomplish on one easily referenced notepad. Tasks MUST be small in scope and be able to be completed within a day.&lt;/li&gt;
&lt;li&gt;Before starting a task, plan how to accomplish that task in step-by-step, excruciating detail.&lt;/li&gt;
&lt;li&gt;Construct a "mise-en-place" for each task, both in terms of physical and mental preparation needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;This workflow forces the following to happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When I take the time to plan out exactly what I need to do to accomplish a certain task, I visualise potential pitfalls and am able to think through everything except the nitty gritty of the tasks, leading to an easy step into "deep work" mode when I am executing without having to stop and think through the problem mid-task.&lt;/li&gt;
&lt;li&gt;The mise step lets me mentally and physically reduce the barrier to entry of starting the task. The philosophy of reducing the ratio of perceived effort to reward (task completion) has already helped me form my workflows in the past.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Okay that's it see you in a month and I'll update if this works&lt;/p&gt;

</description>
      <category>adhd</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Live Syncing to a Git Repository with a VS Code Extension</title>
      <dc:creator>Mohammad Rafiq</dc:creator>
      <pubDate>Mon, 10 Feb 2025 01:03:03 +0000</pubDate>
      <link>https://dev.to/bwfiq/live-syncing-to-a-git-repository-with-a-vs-code-extension-3p8m</link>
      <guid>https://dev.to/bwfiq/live-syncing-to-a-git-repository-with-a-vs-code-extension-3p8m</guid>
      <description>&lt;p&gt;by Mohammad Rafiq&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last updated on 10/02/2025&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Psst! The source code talked about below is up at &lt;a href="https://github.com/bwfiq/git-livesync" rel="noopener noreferrer"&gt;https://github.com/bwfiq/git-livesync&lt;/a&gt; :)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Foreword
&lt;/h2&gt;

&lt;p&gt;As mentioned in the last post, I keep my notes in git repositories. I originally used &lt;a href="https://obsidian.md/" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt; for years as my note-taking application of choice after migrating away from Google Keep, using the vast library of community plugins (namely &lt;a href="https://github.com/vrtmrz/obsidian-livesync" rel="noopener noreferrer"&gt;obsidian-livesync&lt;/a&gt; and &lt;a href="https://github.com/Vinzent03/obsidian-git" rel="noopener noreferrer"&gt;obsidian-git&lt;/a&gt;) to back up and sync my notes on an interval to my 3 remotes; GitHub, my private &lt;a href="https://github.com/go-gitea/gitea" rel="noopener noreferrer"&gt;Gitea&lt;/a&gt; instance for my private "second brain" type notes, and my &lt;a href="https://github.com/redimp/otterwiki/" rel="noopener noreferrer"&gt;Otterwiki&lt;/a&gt; instance (a wiki that runs on a git server of markdown files).&lt;/p&gt;

&lt;p&gt;A recent issue I've run into is that since I started working my first big boy job, I've been unable to download or install any software. I already foresaw this, though, as the main reason I use Obsidian (other than how great it is as a note-taking app) is that all the notes are stored in a very transparent directory structure as markdown files. I simply spun up a &lt;a href="https://github.com/coder/code-server" rel="noopener noreferrer"&gt;code-server&lt;/a&gt; instance, cloned my notes repository, and was off to the races.&lt;/p&gt;

&lt;p&gt;The actual issue was me missing my automatic sync. Thankfully, I almost immediately found &lt;a href="https://github.com/lostintangent/gitdoc" rel="noopener noreferrer"&gt;GitDoc&lt;/a&gt;, which I mentioned in the last post as something I was looking into to replicate the live sync functionality I was used to.&lt;/p&gt;

&lt;p&gt;I used it quite happily for a few hours, until I realised it stopped committing my changes randomly and would sometimes not even work even on startup. I dug through the VS Code logs, but none of my extensions were logging any warnings or errors.&lt;/p&gt;

&lt;p&gt;Of course, I did the sane thing at this point, and manually committed my changes in a normal, human way.&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;Just kidding. Here's how I made my own extension to watch for file changes in VS Code and automatically syncs them to a configured remote.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;By the way, when writing this post, I found a &lt;a href="https://github.com/lostintangent/gitdoc/issues/88" rel="noopener noreferrer"&gt;Github Issue&lt;/a&gt; that gave me more information about why GitDoc wasn't working for me; its latest version is for some reason incompatible with &lt;a href="https://github.com/foambubble/foam" rel="noopener noreferrer"&gt;Foam&lt;/a&gt;, which I use to get Obsidian-like functionality in VS Code. Unfortunately, it seems like there is no resolution yet.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Typescript
&lt;/h2&gt;

&lt;p&gt;So, some background. I grew up on C and Python, and mostly used C in my education, with the occasional module teaching me JS or Java. Even my first dev job in 2022 was as a Unity developer, so I continued in C#. I also recently picked up Rust for a side project.&lt;/p&gt;

&lt;p&gt;If you have ever worked with VS Code or its extensions, you know where this is going. As soon as I cloned the vs code tutorial repository, I figured out I would have to use Typescript. As someone who really disliked using Javascript, I dreaded this (slightly).&lt;/p&gt;

&lt;p&gt;Luckily though this project taught me that the general positive opinion on Typescript by its users is not unfounded. I lowkey loved using it and am excited to try and use it in an actual web dev project.&lt;/p&gt;

&lt;p&gt;Anyway, on to the actual project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing git-livesync
&lt;/h2&gt;

&lt;p&gt;Since I didn't have any experience with Typescript or the &lt;a href="https://code.visualstudio.com/api/references/vscode-api" rel="noopener noreferrer"&gt;VS Code API&lt;/a&gt;, I started with a goal of a prototype that could live sync any changed files to a git repository, regardless of the implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The MVP
&lt;/h3&gt;

&lt;p&gt;I quickly followed the VS Code extension tutorial, which basically taught me jack shit except the file structure of VS Code extensions. (No hate to the authors, though. The people at VS Code write damn good tutorials; I learned a lot from their Django one)&lt;/p&gt;

&lt;p&gt;Regardless, I started the project by using the &lt;a href="https://www.npmjs.com/package/generator-code" rel="noopener noreferrer"&gt;yeoman generator for vs code extensions&lt;/a&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yo code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Take note you need to have &lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;NVM and NPM&lt;/a&gt; set up on your local machine.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This set up my local development environment with the proper project structure. I'm lazy and didn't bother with writing unit tests for this project, so the only files we are concerned with are &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;src/extension.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; is a node thing that defines a lot of information about the project, and VS Code extends this by including extension metadata for the marketplace and the extension view, as well as using it to add &lt;a href="https://code.visualstudio.com/api/references/activation-events" rel="noopener noreferrer"&gt;Activation Events&lt;/a&gt; and &lt;a href="https://code.visualstudio.com/api/references/contribution-points#contributes.configuration" rel="noopener noreferrer"&gt;configuration settings&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/&lt;/code&gt; is gonna contain all our typescript files that define the extension's logic, and &lt;code&gt;extension.ts&lt;/code&gt; is compiled to &lt;code&gt;out/extension.js&lt;/code&gt;, which is the entrypoint defined in our &lt;code&gt;package.json&lt;/code&gt; for the extension.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As an aside, I have recently adopted the &lt;a href="https://www.conventionalcommits.org/" rel="noopener noreferrer"&gt;Conventional Commit&lt;/a&gt; syntax for my commits, hoping that this will improve both readability of my repos and also force me to think more carefully about what I commit. This first task of this project however, was totally not in sync with this philosophy; I just wrote the entire intended logic of the extension, tested it, and pushed it. Later commits are better separated.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We use the VS Code API to watch the filesystem for changes, and this does not change throughout the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Creates a file system watcher&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;watcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFileSystemWatcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Run our logic on events sent from the watcher&lt;/span&gt;
&lt;span class="nx"&gt;watcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDidChange&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;handleFileEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;changed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;watcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDidCreate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;handleFileEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;watcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDidDelete&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;handleFileEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deleted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;watcher&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For now, I ran a simple terminal window that runs my git commands, though I will be changing this later to use &lt;a href="https://www.npmjs.com/package/simple-git" rel="noopener noreferrer"&gt;simple-git&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleFileEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// We'll first get the path of the file that was modified.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fsPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;relative_file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspaceFolders&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 class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fsPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;file_path&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// This handles the .gitignore exclusions; it will be explained in the next section.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ignores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relative_file_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commit_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;relative_file_path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// We create a terminal and send the commands.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;terminal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTerminal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Git LiveSync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`cd &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspaceFolders&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 class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fsPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`git pull &amp;amp;&amp;amp; git add . &amp;amp;&amp;amp; git commit -m "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;commit_message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" &amp;amp;&amp;amp; git push`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, this MVP includes one of the key features of respecting excluded files and folders from the .gitignore. This ensures we only initiate a commit-and-sync when only files we want to commit are modified. We'll use the ignore module for this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ignore&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ignore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// We first retrieve the .gitignore...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gitignorePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspaceFolders&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 class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fsPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.gitignore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// then extract the exclusion patterns from it.&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;git_ignore_patterns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gitignorePath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gitignoreContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gitignorePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;git_ignore_patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gitignoreContent&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&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 class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;git_ignore_patterns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Include the .git/ directory as well.&lt;/span&gt;
&lt;span class="c1"&gt;// We then add these patterns to our ignorer to use.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;git_ignore_patterns&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;You can see the extension.ts at this point &lt;a href="https://github.com/bwfiq/git-livesync/blob/9965b58500c0cb786521090609ce2d7babb318bd/src/extension.ts" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Refactoring
&lt;/h3&gt;

&lt;p&gt;With this simple implementation of the extension's goals, we now have a working MVP. Whenever a file is changed, created, or deleted, the extension commits-and syncs (aka pulling changes from the remote before committing and pushing).&lt;/p&gt;

&lt;p&gt;Here's a non-exhaustive list of the problems right now before we can use this extension, though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is no cooldown on the file watching or terminal functionality; it will spam commits as soon as any file is changed for every single change detected.&lt;/li&gt;
&lt;li&gt;The current implementation of the git command runner spawns new VS Code terminals that are visible in the terminal view in VS Code. This is technically not an issue, but it's ugly.&lt;/li&gt;
&lt;li&gt;This functionality does not respect what workspace you are in, instead running the commands on any change in any workspace, irrespective of if you want it to or if the workspace even has a git repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanna fix these issues, but first, I decided to refactor the current logic into a collection of source files. We will handle git operations using a GitHandler class in &lt;code&gt;src/gitHandler.ts&lt;/code&gt;, exclusion patterns using a IgnoreHandler class in &lt;code&gt;src/ignoreHandler.ts&lt;/code&gt;, and file system watching using a Watcher class in &lt;code&gt;src/watcher.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For the sake of brevity, I won't include the refactor here, but you can see the commit &lt;a href="https://github.com/bwfiq/git-livesync/commit/55dcb8abfc2a75403e37522589f37eadf8a37a03" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was tempted to improve the code during the refactoring process as I am wont to do, but in keeping spirit with Conventional Commit syntax, I refrained from doing so. However, we are now in a great position to begin working on the issues that we have identified.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improving
&lt;/h3&gt;

&lt;p&gt;My next step was to tackle the issues of workspace-specific functionality and the cooldown between running git commands, which can both be solved with VS Code configuration settings. I want the extension to be disabled by default, and have the user enable it in the workspaces they want to have live synced (exactly like GitDoc!).&lt;/p&gt;

&lt;p&gt;Doing this for a VS Code extension necessitates first defining the config settings in the &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"configuration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;We&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;our&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;under&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;different&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;just&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;want&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;them&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;under&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;heading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;so&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extension&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git-livesync"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;setting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;later&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;through&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;API&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"git-livesync.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CAUTION: DO NOT ENABLE THIS FOR THE USER! Specifies whether to enable this extension in this workspace."&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"git-livesync.commitDelay"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Set the time in seconds since the last commit before the extension will auto commit your changes."&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't mention it in the previous section, but I also wrote a small &lt;code&gt;src/utils.ts&lt;/code&gt; for holding global variables and functions. I make use of it now to look up the config settings and also initialise a reference to them upon the extension starting up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;commitDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCommitDelay&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;commitDelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This lets us call getCommitDelay() from anywhere else in the code.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initializeCommitDelay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// We call this in the activate function to make sure the first time getCommitDelay() is called, it has a value.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configuredCommitDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git-livesync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;commitDelay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;configuredCommitDelay&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We implement error checking here in case we fucked up our package.json.&lt;/span&gt;
    &lt;span class="c1"&gt;// Ordinarily, though, there is no way a number could be here.&lt;/span&gt;
    &lt;span class="nx"&gt;commitDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;configuredCommitDelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// This subscribes to the VS Code API's event system, updating the value of commitDelay whenever the config changes.&lt;/span&gt;
  &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDidChangeConfiguration&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;affectsConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git-livesync.commitDelay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git-livesync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;commitDelay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;newDelay&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;commitDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newDelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// git-livesync.enabled follows the exact same code as above&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;In hindsight, a better implementation would have been looking up the config setting everytime our getter functions were called instead of having it subscribe to the configuration event.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now we can call the functions and retrieve our config settings, we can use them. Here's an excerpt from &lt;code&gt;watcher.ts&lt;/code&gt; that shows how we use the enabled config setting to only call the commit function when the config setting is enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ignoreHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ignores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relativeFilePath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getEnabled&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Calling the function from utils.ts&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gitHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relativeFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The if statements are split up here so I can add debug messages if a file is supposed to be committed but the extension is disabled. I have removed the debug statements from the code snippets in this article, but you can feel free to dig through them in the source repo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The way I chose to implement the original cooldown was unfortunately pretty shoddy. In essence, it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// If we can get the last commit's timestamp (through git log) and it was longer ago than the configured commit delay...&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastCommitTimestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastCommitTimestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;getCommitDelay&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...run the commit commands in the terminal.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Again, the weird not operator in this if statement is due to me having debug messages.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This initial attempt at a debounce mechanism was not great. The keen eyed among you will spot the immediate problem: some file changes get skipped completely. To explain this, imagine if you were using the extension in this current state. You would start editing a file, type one character that initiates a commit, and the next few characters you typed would be skipped while the delay resolves. This is not a problem on shorter delays, but given a longer configured delay or a slower (in terms of network speed and compute) machine, this could potentially lead to some work being lost in the worst case, or even possible merge conflicts if you are using the extension on multiple machines. Luckily, I do fix this later, so keep reading.&lt;/p&gt;

&lt;h3&gt;
  
  
  simple-gitting
&lt;/h3&gt;

&lt;p&gt;I obviously wasn't very happy about this implementation, because the next four hours and five commits (minus documentation or chores) were all about trying to fix this. I'm not used to working with node, and generally try and implement everything from the ground up like an idiot low level programmer, so I had no idea about node's wonderful library of packages that can do everything I wanted better and faster. Introducing &lt;a href="https://www.npmjs.com/package/simple-git" rel="noopener noreferrer"&gt;simple-git&lt;/a&gt;! Instead of me trying to run git commands in the VS Code terminal or using node's child process exec, simple-git lets me literally just call git.pull() and everything works automagically.&lt;/p&gt;

&lt;p&gt;Anyway, here's what the refactor looked like in &lt;code&gt;gitHandler.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// I initialise the simpleGit object in the class constructor and give it some parameters.&lt;/span&gt;
&lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ExtensionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SimpleGitOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;baseDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getWorkspacePath&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// So it will use the current workspace (as is the point of the extension)&lt;/span&gt;
    &lt;span class="na"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// So it will use all your sneaky little configs&lt;/span&gt;
    &lt;span class="na"&gt;maxConcurrentProcesses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// This part was in the example so I left it in&lt;/span&gt;
    &lt;span class="na"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Trims whitespace from the commits. Completely unnecessary but also it was in the example&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;simpleGit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Here is the function to commit and sync to the remote. It is now approximately 435% prettier.&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Here's a much more elegant debouncing solution.&lt;/span&gt;
  &lt;span class="c1"&gt;// Only one commit process runs at a time (which makes me leaving in maxConcurrentProcesses even dumber) and...&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastCommitTimestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLastCommitTimestamp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeSinceLastCommit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastCommitTimestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeDelta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCommitDelay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;timeSinceLastCommit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sleepDuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&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 class="nx"&gt;timeDelta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;sleepDuration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ...we just queue up the last commit so it commits all the changes after the delay.&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commitMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commitMessage&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Doesn't this look a million times better&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getLastCommitTimestamp&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// git log -1 --format=%ct returns the UNIX timestamp of the latest commit.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--format=%ct&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// It took me like 30 minutes to figure out that log.latest.date is not the date, but instead log.latest.hash&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Joking aside, I liked learning about how await and async works in Typescript. The concept of Promises, while not an alien one due to me using callbacks in Unity with C#, is a pretty cool one. It's a much more elegant way (IMO) to handle callbacks than all the other implementations I have seen.&lt;/p&gt;

&lt;p&gt;Anyway, with my gitHandler class being refactored to be #sexy, we can review what we've achieved so far: an extension that watches for file changes, commits and syncs those changes to a git remote, waits to commit until a configured time has passed, and is also able to be enabled in specific workspaces. We have basically achieved all the functionality we originally planned!&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentating
&lt;/h3&gt;

&lt;p&gt;I think its a good time to mention that I preceded each commit, whether it be a refactor, feature, or fix, with an update to README.md. I had a known issues and TODO section that I religiously kept updated. I kinda used to hate planning out what I wanted to do before I did it, but I have to say that setting goals for the next commit and limiting myself to them have definitely led to some better habits, and improved productivity probably by 2 or 3x. This project took me only about ~8 hours of work, compared to the rough week I would have spent on it before I started sticking to best practises. Anyway, back to the project:&lt;/p&gt;

&lt;h2&gt;
  
  
  Polishing
&lt;/h2&gt;

&lt;p&gt;There was one more thing I wanted to do before I built the .vsix and tested it on any other repos than a burner; making the extension activate on startup rather than how it currently activated, which was by running a command from the command palette. I have no idea why it took this long for me to figure out that this was literally one line of code to be added to my &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"activationEvents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"workspaceContains:**/.git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;just&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;case&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"onStartupFinished"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;, but this "feature" ended up being the quickest one to implement at a whopping 10 minutes. Thank you VS Code for making it as easy as possible for the Cro Magnon devs of the world &amp;lt;3&lt;/p&gt;

&lt;h3&gt;
  
  
  Packaging
&lt;/h3&gt;

&lt;p&gt;Anyway, at that was left to do was to package the extension as an installable .vsix file. This is easily accomplished with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vsce package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The VSIX file is dropped in the root project directory, and the console output helpfully informs you what is packaged, serving as a useful reference for what to exclude using the &lt;code&gt;.vscodeignore&lt;/code&gt; file, which for me, ended up being:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.vscode/**
.vscode-test/**
src/**
node_modules
out
webpack.config.js
esbuild.js
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/eslint.config.mjs
**/*.map
**/*.ts
**/.vscode-test.*
core
not
output*.*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is messy because I just appended exclude patterns to whatever the yeoman generator gave me to start with.&lt;/p&gt;

&lt;p&gt;Along with this, I also (following the &lt;a href="https://code.visualstudio.com/api/working-with-extensions/bundling-extension" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt;) bundled my extension, which reduced the size of the packaged VSIX file from 10.9MB to 43.63KB (a 2498% size reduction!).&lt;/p&gt;

&lt;p&gt;Excited and quivering at finally being able to release a finished piece of software for the first time, I wrote a CI pipeline with Github Actions (which promptly &lt;a href="https://github.com/bwfiq/git-livesync/actions/runs/13183413038/job/36799643138" rel="noopener noreferrer"&gt;failed&lt;/a&gt;) and tagged my latest commit with v0.0.1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Releasing
&lt;/h3&gt;

&lt;p&gt;Actually, that was a lie because I had to fuck with git for an hour before I figured out how to use release branches and SemVer tagging. This is how I ended up doing it (thank you LLMs):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Begin on the latest commit on your main branch&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; release/v0.0.1
&lt;span class="c"&gt;# Do whatever changes you need to make for your release (I removed debug messages and updated the readme)&lt;/span&gt;
git commit
git tag v0.0.1 &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Release v0.0.1"&lt;/span&gt; &lt;span class="c"&gt;# Tags the latest commit&lt;/span&gt;
git push origin v0.0.1 &lt;span class="c"&gt;# Or alternatively git push --tags if you have nothing to commit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that I officially had a commit up on my GitHub remote with the v0.0.1 tag, all I had to do was write a quick changelog (thank you LLMs) and publish it as a release, adding my VSIX file as a binary.&lt;/p&gt;

&lt;p&gt;You can find the release &lt;a href="https://github.com/bwfiq/git-livesync/releases/tag/v0.0.1" rel="noopener noreferrer"&gt;here&lt;/a&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup
&lt;/h3&gt;

&lt;p&gt;Before we end, I'll just cover what else I did with the project before abandoning it for the foreseeable future.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout main
git merge release/v0.0.1
git branch &lt;span class="nt"&gt;-d&lt;/span&gt; release/vX.Y.Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This cleaned up my repository by getting rid of the release branch, merging the commits into my main.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As sardonic as my tone is in this post, I really enjoyed working on this project. First of all, it's my first actual piece of software that I have released, which feels grand. Also, it helps me feel more like a developer now that I have gone through the rite of passage of being annoyed at something and being actually able to code it away.&lt;/p&gt;

&lt;p&gt;It's a shame I couldn't just use GitDoc, but I don't regret it. I learned a new language and understand the VS Code extension framework, and by proxy Node and NPM much better. I'm also pretty proud of the entire project only taking two days and for how focused I was on implementing the core functionality and not getting sidetracked as usual, either by irrelevant features, avoidable bugs, or other projects.&lt;/p&gt;

&lt;p&gt;The source code for this project is up at &lt;a href="https://github.com/bwfiq/git-livesync/" rel="noopener noreferrer"&gt;https://github.com/bwfiq/git-livesync/&lt;/a&gt; under a GNU GPL-3.0 license, so feel free to peruse it. All due credit to GitDoc for inspiring this project, and I hope the maintainers figure out how to fix the problem with Foam. Heck, I might try my hand at contributing to an open-source project other than my own for once.&lt;/p&gt;

&lt;p&gt;Anyway, I'm planning to write a blog post every week this year and work on something either homelab or code-related every day (minus when I'm not at my desk and touching grass instead). You can follow my exploits on &lt;a href="https://github.com/bwfiq/" rel="noopener noreferrer"&gt;Github&lt;/a&gt;. See you guys next week where I will be using the dreaded Python snake&lt;/p&gt;

</description>
      <category>git</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Automating Submodule Updates with Git Hooks</title>
      <dc:creator>Mohammad Rafiq</dc:creator>
      <pubDate>Thu, 06 Feb 2025 06:24:44 +0000</pubDate>
      <link>https://dev.to/bwfiq/automating-submodule-updates-with-git-hooks-4n2m</link>
      <guid>https://dev.to/bwfiq/automating-submodule-updates-with-git-hooks-4n2m</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;by Mohammad Rafiq&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Last updated on 02/02/25 @ 2010hrs UTC+8&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I have a public wiki for my DnD setting up at &lt;a href="https://aenyrathia.wiki/" rel="noopener noreferrer"&gt;https://aenyrathia.wiki/&lt;/a&gt;. It runs on &lt;a href="https://otterwiki.com/" rel="noopener noreferrer"&gt;Otterwiki&lt;/a&gt;, which means I have access to the entire wiki of markdown files as a git repository. While this is amazing for accessing my wiki from anywhere and having version control, it also means I don't have an easy way to add my own private DM notes. &lt;/p&gt;

&lt;p&gt;To this end, I'd like to have my own private repository that pulls in the wiki as a submodule so I can easily reference it for my own notes without having to consult the website. Here's the problem: normally, submodules are locked at a specific commit, which makes sense because they are meant to act as dependencies for the parent repository. The thing is, I'm not working with code in this case. I want the submodule to be constantly up to date and have the latest changes pulled from the wiki.&lt;/p&gt;
&lt;h2&gt;
  
  
  Problems
&lt;/h2&gt;

&lt;p&gt;Right now, I can have &lt;code&gt;git pull&lt;/code&gt; (and the other commands) recursively update the module through a simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config pull.recursesubmodules &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;THe issue now is that because of the way submodules work (primarily the fact that they are a specific checked out commit of the other repository), this doesn't pull in the latest changes from the submodule repository. To actually achieve that, I have to run the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git submodule update &lt;span class="nt"&gt;--remote&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which pulls in the changes from the remote.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawbacks to this method
&lt;/h3&gt;

&lt;p&gt;We have to acknowledge why this "workaround" is not more widely used. I am using git submodules in a non-standard way here, and this approach would lead to some potential issues, revolving around the fact that if the rest of your code depends on these submodules, pulling in random changes could lead to modifications or errors in the logic of the parent repository's code. However, this doesn't apply to this project, because markdown files aren't gonna blow up when I change other files. If you want to use this method though, do be warned!&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation
&lt;/h2&gt;

&lt;p&gt;So, now we have identified the problem and a solution. How do we then prevent having to always run the &lt;code&gt;git submodule update&lt;/code&gt; command every time we pull?&lt;/p&gt;

&lt;h3&gt;
  
  
  Git Hooks!
&lt;/h3&gt;

&lt;p&gt;Literally as I was researching potential solutions for this earlier problem, I was messing with my VS Code configs in an attempt to find out why my .git folder wasn't being shown in the explorer pane. I found out through a &lt;a href="https://medium.com/pareture/show-git-and-other-default-hidden-folders-and-files-in-vs-code-57df151588ea" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; that these excluded folders were in the settings and promptly removed all the default rules, but the post also mentioned why the author wanted to include the .git folder in their explorer, namely because they were using git hooks. &lt;/p&gt;

&lt;p&gt;I had absolutely no idea what these were and skimmed past it since it wasn't related to my issue, but when I was thinking of how I could automate this solution, the git hooks came back to mind as something to look into. Thankfully, I was right in my hunch, and git hooks turned out to be the solution. All I had to do was drop a shell script named &lt;code&gt;post-merge&lt;/code&gt; into the .git/hooks folder and make it executable, and whatever commands were in that file would be run upon a pull command.&lt;/p&gt;

&lt;p&gt;The contents I put in my &lt;code&gt;.git/hooks/post-merge&lt;/code&gt; script were as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git submodule update &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; &lt;span class="nt"&gt;--remote&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice the --init and --recursive flags being included which weren't mentioned above, but all they are for is for when you clone my parent repository (which doesn't automatically initialise the submodule repository) and are more for if I forget to also init the submodule when cloning this repo next time.&lt;/p&gt;

&lt;p&gt;With that being said, now whenever I pull the repository, the submodule nicely populates itself with whatever changes has been made to the wiki.&lt;/p&gt;

&lt;p&gt;Next steps are to see if I can't make this easier to work with by using the &lt;a href="https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gitdoc" rel="noopener noreferrer"&gt;GitDoc&lt;/a&gt; extension to be able to work with my wiki like I'm used to in Obsidian with livesync!&lt;/p&gt;

</description>
      <category>git</category>
      <category>automation</category>
      <category>markdown</category>
      <category>dnd</category>
    </item>
  </channel>
</rss>
