<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <link href="https://callumbodels.xyz/feed.xml" rel="self" />
  <link href="https://callumbodels.xyz/" />
  <id>https://callumbodels.xyz/</id>
  <entry>
    <title>Using isync</title>
    <link href="https://callumbodels.xyz/posts/using-isync/" />
    <updated>2019-09-18T00:00:00Z</updated>
    <id>https://callumbodels.xyz/posts/using-isync/</id>
    <content type="html">&lt;p&gt;Isync is a command-line utility used for synchronising (downloading) remote and local mailboxes. Isync can be used as part of a tool-chain to be able to retrieve, store and send mail all from the command line.&lt;/p&gt;
&lt;h2&gt;Benefits&lt;/h2&gt;
&lt;p&gt;The benefits of using isync combined with neomutt(a command-line mail client) over just neomutt is that isync stores a local cache, so when offline, you can still view your emails. There is also the benefit of increased speed and a reduction in bandwidth usage; as isync only downloads new emails to the local cache. Whereas neomutt, when hooked into IMAP, doesn&#39;t keep a local cache. Therefore it needs to re-download all emails each time; meaning you can&#39;t view your email without an internet connection.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;For Arch Linux isync is in the main repositories and can be installed easily via pacman.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; pacman &lt;span class=&quot;token parameter variable&quot;&gt;-S&lt;/span&gt; isync&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Setting up isync&lt;/h2&gt;
&lt;p&gt;Isync&#39;s configuration file resides in &lt;code&gt;${HOME}/.mbsyncrc&lt;/code&gt;. Within this configuration file, you need to define how to download each mailbox and each &#39;channel&#39; for every email account.&lt;/p&gt;
&lt;p&gt;We will start by defining the remote IMAP servers configuration. As an example, I will pretend to be setting up this configuration for a Gmail account.&lt;/p&gt;
&lt;p&gt;Note to use isync with Gmail you need to &#39;Enable Less Secure Apps&#39;, you can follow the steps here &lt;a href=&quot;https://www.dev2qa.com/how-do-i-enable-less-secure-apps-on-gmail/&quot;&gt;https://www.dev2qa.com/how-do-i-enable-less-secure-apps-on-gmail/&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;IMAP setup&lt;/h2&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;# Global Channel configuration.
Create Both
Expunge Both
Remove Both
Sync All
SyncState *

################################
####### example@gmail.com ######
################################
# IMAP setup
IMAPAccount example-gmail
Host imap.gmail.com
User example@gmail.com
PassCmd &quot;pass Communications/Gmail&quot;
SSLType IMAPS

...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The variables under &lt;code&gt;# Global Channel configuration.&lt;/code&gt; will be explained later when talking about channel configuration.&lt;/p&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;################################
####### example@gmail.com ######
################################&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Is not required, but I use it to denote the different sections of the configuration file for the various emails; helping me to find what I want quickly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;IMAPAccount example-gmail&lt;/code&gt; Denotes the IMAP&#39;s account name, and tells isync the following options are related to IMAP. The name does not affect anything; so you can name it anything you want.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Host imap.gmail.com&lt;/code&gt; Denotes the IMAP server&#39;s URL. The IMAP URL is email provider-specific, check your email providers website to find the provider&#39;s specific URL.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;User example@gmail.com&lt;/code&gt; Is the username to login in as to the IMAP server. This is typically your email, but it can vary depending on the provider.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PassCmd &amp;quot;pass Communications/Gmail&amp;quot;&lt;/code&gt; PassCmd tells isync the shell command it can execute to fetch the IMAP servers password for the user. I use the utility &#39;pass&#39; to manage all my passwords, it is a useful utility, and you should check it out.&lt;/p&gt;
&lt;p&gt;However, you can do &lt;code&gt;Pass examplePassword&lt;/code&gt; if you would prefer to enter the password in plain text. But be warned now anyone who can get hold of your configuration file has YOUR PASSWORD!&lt;/p&gt;
&lt;p&gt;Note you don&#39;t need to specify either &lt;code&gt;PassCmd&lt;/code&gt; or &lt;code&gt;Pass&lt;/code&gt; if the options are missing when executing isync&#39;s command &lt;code&gt;mbsync&lt;/code&gt; then you are just prompted for the password.&lt;/p&gt;
&lt;h2&gt;Local &amp;amp; Remote storage setup&lt;/h2&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;...

# Remote storage setup
IMAPStore example-gmail-remote
Account example-gmail

# Local storage setup
MaildirStore example-gmail-local
Path ~/.neomutt/mail/example-gmail/
Inbox ~/.neomutt/mail/example-gmail/inbox

...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have set up the IMAP server configuration, we will move on to define the properties of remote and local locations we will be synchronising.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;IMAPStore example-gmail-remote&lt;/code&gt; Denotes the IMAP&#39;s store name, and tells isync the following configurations are related to the IMAP store. Again the name does not affect anything; so you can name it anything you want.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Account example-gmail&lt;/code&gt; Specifies which IMAP configuration to use for the IMAPStore, here we are referencing the IMAPAccount configuration we specified earlier, using the name we gave it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MaildirStore example-gmail-local&lt;/code&gt; Gives your local Maildir Store a name, also informing isync a Maildir Store is about to be configured. As previously stated; the alias can be anything you want.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Path ~/.neomutt/mail/example-gmail/&lt;/code&gt; tells isync the location on the file system to store the local mail cache.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Inbox ~/.neomutt/mail/example-gmail/inbox&lt;/code&gt; defines the location to contain the local cache&#39;s inbox folder. Typically it is inside the Path directory defined above.&lt;/p&gt;
&lt;p&gt;NOTE - &lt;code&gt;Path&lt;/code&gt; needs to end in a &lt;code&gt;/&lt;/code&gt; whereas &lt;code&gt;Inbox&lt;/code&gt; does not.&lt;/p&gt;
&lt;h2&gt;Setting up the channels to synchronise&lt;/h2&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;...

# Inbox
Channel example-gmail-inbox
Master :example-gmail-remote:
Slave  :example-gmail-local:inbox

# Sent
Channel example-gmail-sent
Master :example-gmail-remote:&quot;[Gmail]/Sent Mail&quot;
slave  :example-gmail-local:sent

# Drafts
Channel example-gmail-drafts
Master :example-gmail-remote:&quot;[Gmail]/Drafts&quot;
slave  :example-gmail-local:drafts

# Trash
Channel example-gmail-trash
Master :example-gmail-remote:&quot;[Gmail]/Bin&quot;
slave  :example-gmail-local:trash

# Spam
Channel example-gmail-spam
Master :example-gmail-remote:&quot;[Gmail]/Spam&quot;
slave  :example-gmail-local:spam

...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have defined the local/remote storage locations and entered the IMAP credentials; we now need to define which &#39;channels&#39; (folders) we want to synchronise with our local cache. We don&#39;t have to download every remote folder we can be selective, change the folders name and various other custom configurations to meet your needs!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Channel example-gmail-inbox&lt;/code&gt; Denotes the channels name and tells isync the following parameters are related to this channel. Again the name does not affect anything; so you can name it anything you want.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Master :example-gmail-remote:&lt;/code&gt; Defines the Master folder for the channel as the remote IMAP, from which it will take directions from. We reference the remote IMAPStore by the name we gave it prior. The option follows the format &lt;code&gt;Master :&amp;lt;remote IMAP&amp;gt;:&amp;lt;folder&amp;gt;&lt;/code&gt; because we are defining the inbox which is the default we do not need to specify the folder, but for other folders, they will need to be detailed.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Slave :example-gmail-local:inbox&lt;/code&gt; Declares the slave repository as the local cache via the naming we gave it. Like Master the format has the format &lt;code&gt;Slave :&amp;lt;local cache name&amp;gt;:&amp;lt;folder&amp;gt;&lt;/code&gt;. However, note that even though it is still the inbox channel being defined, you need to give the folder to save it to. The remote host will be synchronised to &lt;code&gt;$PATH/&amp;lt;folder&amp;gt;,&lt;/code&gt; i.e. the path we defined earlier for MaildirStore.&lt;/p&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;Channel example-gmail-sent
Master :example-gmail-remote:&quot;[Gmail]/Sent Mail&quot;
slave  :example-gmail-local:sent&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Above is another example of a channel; for this, we are defining the sent channel. Note the remote Master now contains a folder, this is the same naming and structure as on the remote host. The remote data in &lt;code&gt;[Gmail]/Sent Mail&lt;/code&gt; is now synchronised to our local cache at &lt;code&gt;$PATH/sent&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Global/Local Channel Configurations&lt;/h2&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;# Global Channel configuration.
Create Both
Expunge Both
Remove Both
Sync All
SyncState *

...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the beginning of the configuration file, there were the above options which I did not explain. They are configurations for channels, having them at the top of the file makes them global options which apply to every channel.&lt;/p&gt;
&lt;p&gt;However, you can override global settings by defining them locally for a channel.&lt;/p&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;Channel example-gmail-sent
Master :example-gmail-remote:&quot;[Gmail]/Sent Mail&quot;
slave  :example-gmail-local:sent
Create None

...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the channel above the global setting &lt;code&gt;Create Both&lt;/code&gt; has been overridden by &lt;code&gt;Create None&lt;/code&gt;, this local option is set for this channel only, and all the other channels use the global setting.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Create Both&lt;/code&gt; Tells isync to create missing mailboxes automatically. Create can take the options &lt;code&gt;{None|Master|Slave|Both}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Expunge Both&lt;/code&gt; Permanently deletes all messages marked for deletion. Expunge can take the options &lt;code&gt;{None|Master|Slave|Both}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Remove Both&lt;/code&gt; Notifies isync if it should propagate mailbox deletions. Remove can take the options &lt;code&gt;{None|Master|Slave|Both}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Sync All&lt;/code&gt; Informs isync to synchronise all operations, pull, push, delete etc.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SyncState *&lt;/code&gt; Tells isync to keep the channel&#39;s synchronisation state files inside the channel&#39;s local cache of the folder, in the file &lt;code&gt;.mbsyncstate&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Grouping channels&lt;/h2&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;
...

Group example-gmail
Channel example-gmail-inbox
Channel example-gmail-sent
Channel example-gmail-drafts
Channel example-gmail-trash
Channel example-gmail-spam&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can group channels into a group. Meaning you can use the command &lt;code&gt;mbsync &amp;lt;group&amp;gt;&lt;/code&gt; to synchronise all the channels in that group. So you can only update select email accounts or specific folders across all accounts if we are limited by download speed or size etc.&lt;/p&gt;
&lt;p&gt;However you don&#39;t need to define any groups, you can use &lt;code&gt;mbsync -a&lt;/code&gt; to synchronise all channels in the mbsync configuration file.&lt;/p&gt;
&lt;h2&gt;Multiple Email&#39;s&lt;/h2&gt;
&lt;p&gt;As I previously alluded to when mentioning the inclusion of :&lt;/p&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;################################
####### example@gmail.com ######
################################&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can configure multiple email accounts from which to synchronise channels. To achieve this, you define them sequentially one after another. I use the header to signify where one email&#39;s configuration stops and another begins. I use it to quickly find my place among a wall of text annotating several account&#39;s configuration and options.&lt;/p&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;...

################################
####### example@gmail.com ######
################################

...

################################
##### example@outlook.co.uk ####
################################

...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;TIP: How to find out remote IMAP folder names&lt;/h2&gt;
&lt;p&gt;Sometimes you have no idea of the remote IMAP folders naming or the structure, meaning you can not configure the channels for the email address.&lt;/p&gt;
&lt;p&gt;To overcome this problem, you can download the whole remote folder, without altering the structure or naming, then using the structure of the unaltered local cache you can work out how to configure the channels you desire.&lt;/p&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;...

# Local storage
MaildirStore example-gmail-local
Subfolders Verbatim
Path ~/.neomutt/mail/example-gmail/
Inbox ~/.neomutt/mail/example-gmail/inbox

Channel example-gmail-default
Master :example-gmail-remote:
Slave  :example-gmail-local:
# Include everything
Patterns &quot;*&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Subfolders Verbatim&lt;/code&gt; specifies that the local cache should use the directory hierarchical structure &lt;code&gt;$PATH/top/sub/subsub&lt;/code&gt;. Meaning it will copy the remote structure verbatim.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Patterns &amp;quot;*&amp;quot;&lt;/code&gt; is some simple regex telling isync to synchronise every remote folder.&lt;/p&gt;
&lt;p&gt;Notice how on the end of the Slave for the channel that there is no directory specified, meaning it will default to the remote naming and structure.&lt;/p&gt;
&lt;p&gt;Now you need to synchronise the email then inspect your local cache to know the remote server&#39;s structure and naming conventions.&lt;/p&gt;
&lt;h2&gt;TIP: Documentation&lt;/h2&gt;
&lt;p&gt;If you want to know more about a specific configuration and the options available you can read the man page &lt;code&gt;man mbsync&lt;/code&gt; and scroll past the command line usage description to get to the configuration file documentation!&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/index.php/Isync&quot;&gt;https://wiki.archlinux.org/index.php/Isync&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.installgentoo.com/wiki/A_modern_mutt_setup&quot;&gt;https://wiki.installgentoo.com/wiki/A_modern_mutt_setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/chandraratnam/f00ab7d4a5298830f692021964fdb99f&quot;&gt;https://gist.github.com/chandraratnam/f00ab7d4a5298830f692021964fdb99f&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lukespear.co.uk/mutt-multiple-accounts-mbsync-notmuch-gpg-and-sub-minute-updates/&quot;&gt;https://lukespear.co.uk/mutt-multiple-accounts-mbsync-notmuch-gpg-and-sub-minute-updates/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Linux Foundation Certified SysAdmin</title>
    <link href="https://callumbodels.xyz/posts/lfcs-thoughts/" />
    <updated>2020-02-13T00:00:00Z</updated>
    <id>https://callumbodels.xyz/posts/lfcs-thoughts/</id>
    <content type="html">&lt;p&gt;I recently took and passed my &lt;a href=&quot;https://training.linuxfoundation.org/certification/linux-foundation-certified-sysadmin-lfcs/&quot;&gt;Linux Foundation Certified SysAdmin&lt;/a&gt; certification.
The certification examination consists of SSHing into a remote machine and performing a set of tasks.
The tasks cover all the basic operations you would be expected to perform while working as a system administrator; creating and configuring file systems, using SELinux/AppArmor, adding users/groups, setting up RAID, &lt;em&gt;etc&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I believe practical examinations are an improvement over written examinations at demonstrating the holder&#39;s knowledge of Linux.
However, I have a singular issue with the LFCS exam, that being the maximum time allowance.
During the examination, you are asked to perform specific exercises, with no access to the internet, you can easily spend ten or fifteen minutes scrolling through the man pages.
Yet you could find the specific argument/flag within seconds &lt;em&gt;via&lt;/em&gt; a search engine.&lt;/p&gt;
&lt;p&gt;Allowing the usage of the internet to search for command arguments/flags quickly, this would stop capable system administrators from being bogged down and failing just because they could not remember a specific command/argument.
This would make the examination easier but they could counter-balance this by increasing the complexity of the questions, so someone just using Google not understanding what they are doing can not copy and paste their way into becoming certified.&lt;/p&gt;
&lt;p&gt;Because of the specific nature of the questions, I would not advise anyone who has not already used Linux and had years of experience as a systems administrator to take the exam.
Without already having any experience, you would need to memorise a lot of different commands and arguments to be able to keep up with the exam&#39;s pace to achieve a passing mark.
Online I have seen numerous people with no prior experience saying they have re-take it four or five times to pass.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Git Hooks and Rust!</title>
    <link href="https://callumbodels.xyz/posts/git-hooks-and-rust/" />
    <updated>2020-04-22T00:00:00Z</updated>
    <id>https://callumbodels.xyz/posts/git-hooks-and-rust/</id>
    <content type="html">&lt;p&gt;I have started writing a lot of Rust recently, but that is a story for another time.
Like with all my open source projects my Rust repositories have CI pipelines.
These CI pipelines run for every pull request; enforcing basic code quality by checking the code is properly formatted &lt;em&gt;etc&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;As part of my development workflow, I usually have formatting and other basic code quality issues automatically fixed or flagged.
However, sometimes issues slip through.&lt;/p&gt;
&lt;p&gt;Having pipeline failures for issues that otherwise could have quickly been checked and fixed locally, is a problem because of the lengthy feedback loop.
Therefore ideally, you want to stop wasting time by not committing code that does not pass basic code quality checks.&lt;/p&gt;
&lt;p&gt;Enter Git Hooks, which is essentially event-driven execution upon scripts before or after events within Git&#39;s lifecycle.
Git Hooks are part of core Git, so no additional installation is necessary.
For a more in-depth introduction to Git Hooks, you can visit &lt;a href=&quot;https://githooks.com/&quot;&gt;https://githooks.com/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The only requirement for a Git Hook is that it is executable, but Shell/Bash are the most commonly used languages.
To react to a specific event you place an executable inside the &lt;code&gt;.git/hooks/&lt;/code&gt; directory with the event&#39;s name, &lt;em&gt;e.g.&lt;/em&gt; &lt;code&gt;.git/hooks/pre-commit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So we can utilise Git hooks to either perform code quality checks locally before creating the commits or pushing them remotely.
To provide the best developer experience we want to fix or flag a commit before it is created, to avoid rebasing/commit editing headaches.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;pre-commit&lt;/code&gt; hook is fired after the commit message and content have been provided; but before the commit has been created.
So using this event we can alter the contents of a commit, or if the hook exits with a non-zero status code stop it from being created.&lt;/p&gt;
&lt;p&gt;Beneath I will show you multiple examples of using Git hooks being used to enforce formatting.
The examples can easily be modified to automate other tasks such as code linting etc.
Hopefully, afterwards, you will see the power in Git hooks and begin to use them to help you deliver better quality software.&lt;/p&gt;
&lt;h3&gt;Examples&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://callumbodels.xyz/posts/git-hooks-and-rust/#formatting&quot;&gt;Formatting&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://callumbodels.xyz/posts/git-hooks-and-rust/#formatting---single-project&quot;&gt;Formatting - Single Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://callumbodels.xyz/posts/git-hooks-and-rust/#formatting---monorepo&quot;&gt;Formatting - Monorepo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://callumbodels.xyz/posts/git-hooks-and-rust/#formatting---monorepo--workspaces&quot;&gt;Formatting - Monorepo &amp;amp; Workspaces&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Formatting&lt;/h2&gt;
&lt;h3&gt;Formatting - Single Project&lt;/h3&gt;
&lt;p&gt;Checking code formatted is very quick, which makes it one of many ideal code quality checks that can preemptively be reviewed locally.
Using the pre-commit event when we find incorrectly formatted code we have two choices.
Either we can stop the commit from being created, or alter the content so it is correctly formatted.
I prefer to stop the commit being created, otherwise, you are never sure what has actually been committed.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.git/hooks/pre-commit&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Exit immediately on first non-zero status from a command in the script.&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; errexit

&lt;span class=&quot;token comment&quot;&gt;# Ensure all tools being used are available.&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;cargo&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The &#39;cargo&#39; command is not installed. Aborting.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;rustfmt&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The &#39;rustfmt&#39; command is not installed. Aborting.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Check the formatting of all Rust code.&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fmt&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt; -- &lt;span class=&quot;token parameter variable&quot;&gt;--check&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the above as a Git hook it will stop you from committing unformatted code, because &lt;code&gt;cargo fmt --all -- --check&lt;/code&gt; will exit with a non-zero status code cancelling the commit.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;E.g.&lt;/em&gt; with &lt;code&gt;src/main.rs&lt;/code&gt; incorrectly formatted as follows,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/main.rs&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt;  &lt;span class=&quot;token function-definition function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Git hook will print out that &lt;code&gt;src/main.rs&lt;/code&gt; is incorrectly formatted, indicating which lines need to be corrected.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;Diff in /home/rust/hello-world/src/main.rs at line 1:
&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;fn  main() {
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;println!(&quot;Hello, world!&quot;);
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;    }
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;fn main() {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    println!(&quot;Hello, world!&quot;);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then format it correctly before attempting to commit it again.&lt;/p&gt;
&lt;h3&gt;Formatting - Monorepo&lt;/h3&gt;
&lt;p&gt;The Git hook example above falls short when monorepos are introduced which contain multiple packages.
We can perform checks upon each package by programmatically getting each package’s manifest and then checking each package independently.
Sorting the list of package’s manifest before working upon it will also improve usability by providing a consistent output.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.git/hooks/pre-commit&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Exit immediately on first non-zero status from a command in the script.&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; errexit

&lt;span class=&quot;token comment&quot;&gt;# Ensure all tools being used are available.&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;cargo&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The &#39;cargo&#39; command is not installed. Aborting.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;rustfmt&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The &#39;rustfmt&#39; command is not installed. Aborting.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Check the formatting of all Rust code for every project, if in monorepo.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;du&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cargo[.]toml$&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;{print $2}&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;# Check project is correctly formatted.&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fmt&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt; --manifest-path &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${project}&lt;/span&gt;&quot;&lt;/span&gt; -- &lt;span class=&quot;token parameter variable&quot;&gt;--check&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above example works for both monorepos and non-monorepos alike.
However there is a subtle usability issue, the Git hook will fail and exit on the first incorrectly formatted project.
This may mean multiple rounds of attempting to commit before learning of another project that needs formatted.
It would be more useful to list every package that needs formatting the first time.
To make it more usable we can remove exiting on the first error and store the status code, only returning it at the end.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.git/hooks/pre-commit&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Ensure all tools being used are available.&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;cargo&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The &#39;cargo&#39; command is not installed. Aborting.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;rustfmt&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The &#39;rustfmt&#39; command is not installed. Aborting.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Check the formatting of all Rust code for every project, if in monorepo.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;du&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cargo.toml$&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;{print $2}&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;# Check project is correctly formatted.&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fmt&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt; --manifest-path &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${project}&lt;/span&gt;&quot;&lt;/span&gt; -- &lt;span class=&quot;token parameter variable&quot;&gt;--check&lt;/span&gt;
		&lt;span class=&quot;token assign-left variable&quot;&gt;project_is_formatted&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$?&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${project_is_formatted}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ne&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
				&lt;span class=&quot;token assign-left variable&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${project_is_formatted}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$status&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A lot of noise may be generated by all the packages within a monorepo when the formatting issues are reported in detail.
To reduce the noise you can instead list just the files which need formatted, by changing to &lt;code&gt;cargo fmt --all --manifest-path &amp;quot;${project}&amp;quot; --message-format short -- --check&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Formatting - Monorepo &amp;amp; Workspaces&lt;/h3&gt;
&lt;p&gt;The above examples all work fine for separate Rust packages, however, Rust has the idea of workspaces.
The Rust documentation describes a workspace as&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A workspace is a collection of one or more packages that share common dependency resolution (with a shared Cargo.lock), output directory, and various settings such as profiles.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;see &lt;a href=&quot;https://doc.rust-lang.org/cargo/reference/workspaces.html&quot;&gt;https://doc.rust-lang.org/cargo/reference/workspaces.html&lt;/a&gt; for additional details about workspaces.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.git/hooks/pre-commit&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Ensure all tools being used are available.&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;cargo&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The &#39;cargo&#39; command is not installed. Aborting.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;rustfmt&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The &#39;rustfmt&#39; command is not installed. Aborting.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Check the formatting of all Rust code for every project, if in monorepo.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;du&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cargo.toml$&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;{print $2}&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;xargs&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-I&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;cargo metadata --manifest-path {} | jq &quot;.workspace_root&quot;&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uniq&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;# Check project is correctly formatted.&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fmt&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt; --manifest-path &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${project}&lt;/span&gt;&quot;&lt;/span&gt; -- &lt;span class=&quot;token parameter variable&quot;&gt;--check&lt;/span&gt;
		&lt;span class=&quot;token assign-left variable&quot;&gt;project_is_formatted&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$?&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${project_is_formatted}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ne&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
				&lt;span class=&quot;token assign-left variable&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${project_is_formatted}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$status&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>AWS Lambda Language Benchmarking</title>
    <link href="https://callumbodels.xyz/posts/aws-lambda-language-benchmarking/" />
    <updated>2021-02-02T00:00:00Z</updated>
    <id>https://callumbodels.xyz/posts/aws-lambda-language-benchmarking/</id>
    <content type="html">&lt;p&gt;I have seen several articles from years prior detailing the performance for various languages on AWS Lambda.
Frankly, I did not believe the results the articles were reporting.&lt;/p&gt;
&lt;p&gt;With no source code, infrastructure as code, or any details on conducting the performance testing, I could not reproduce and verify the results myself.
Therefore I set out to create an open-source repository containing all the source code, infrastructure, etc.
So anyone can reproduce my findings, even in years to come as the performance characteristics may change.&lt;/p&gt;
&lt;p&gt;To test the various languages, they will all be performing a straightforward task.
They are to take a String as an input and perform 100,000 SHA256 hashes upon it and return the results, to simulate an application performing work.
For larger and more complex applications, I would expect the performance margins to widen even further.&lt;/p&gt;
&lt;p&gt;The implementations for the various languages are what you could expect the typical developer to produce.
No arcane wizardry or obscure performance optimisations have been introduced to tip the results to favour a particular language.&lt;/p&gt;
&lt;p&gt;I will be investigating the languages Node.js, Python3, Go and Rust, this will cover the majority of languages used in AWS Lamdba implementations.&lt;/p&gt;
&lt;p&gt;I am open to pull requests for both additional languages and performance optimisations and will update the article accordingly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gitlab.com/DeveloperC/aws_lambda_language_benchmarking&quot;&gt;https://gitlab.com/DeveloperC/aws_lambda_language_benchmarking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Local Performance&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://callumbodels.xyz/static/img/aws-lambda-language-benchmarking-local-performance.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;AWS Lambda &amp;amp; API Gateway Performance&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://callumbodels.xyz/static/img/aws-lambda-language-benchmarking-aws-performance.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Symbolic links and gitignore</title>
    <link href="https://callumbodels.xyz/posts/symbolic-links-and-gitignore/" />
    <updated>2022-09-27T00:00:00Z</updated>
    <id>https://callumbodels.xyz/posts/symbolic-links-and-gitignore/</id>
    <content type="html">&lt;p&gt;I&#39;ve often seen developers, myself included, use &lt;code&gt;.gitignore&lt;/code&gt; patterns[1] that do not work when used with symbolic links (symlinks)[2]. While not universal, it&#39;s common to leave a trailing &lt;code&gt;/&lt;/code&gt; on the end of a directory name to indicate it is a directory.&lt;/p&gt;
&lt;p&gt;However, if you use a trailing &lt;code&gt;/&lt;/code&gt; in a &lt;code&gt;gitignore&lt;/code&gt; pattern you will have issues with symbolic links. My gut feeling was that Git would treat a symbolic link like what it&#39;s linking to. But since symbolic links are a &#39;special kind of file that points to another file&#39;[2], Git seems to treat them as files. As this has tripped me up a couple of times I want to confirm this and come up with some best practices for the future.&lt;/p&gt;
&lt;h2&gt;Symbolic links&lt;/h2&gt;
&lt;p&gt;So from the results below we can confirm that Git treats symbolic links as files, regardless of what they are linking to. However, another reason for using a trailing &lt;code&gt;/&lt;/code&gt; is to notate the end of the pattern. So we should confirm that omitting it doesn&#39;t cause unintended files or directories to be ignored, before recommending to not include trailing &lt;code&gt;/&lt;/code&gt;&#39;s.&lt;/p&gt;
&lt;br&gt;
&lt;div style=&quot;overflow-x: auto;&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;&lt;code&gt;target&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;target/&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;td&gt;🚫&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbolic Link → File&lt;/td&gt;
&lt;td&gt;🚫&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Directory&lt;/td&gt;
&lt;td&gt;🚫&lt;/td&gt;
&lt;td&gt;🚫&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbolic Link → Directory&lt;/td&gt;
&lt;td&gt;🚫&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;details&gt;
&lt;summary&gt;gitignore-symlink-test.sh&lt;/summary&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; errexit
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; nounset

&lt;span class=&quot;token comment&quot;&gt;# Emoji output: 🚫 = ignored, 📦 = tracked&lt;/span&gt;
&lt;span class=&quot;token function-name function&quot;&gt;check_ignored&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; ls-files &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; --exclude-standard &lt;span class=&quot;token parameter variable&quot;&gt;--others&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-E&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;📦&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;🚫&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function-name function&quot;&gt;run_test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token assign-left variable&quot;&gt;test_dir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;mktemp &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${test_dir}&lt;/span&gt;&quot;&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; init &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
		real_file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		symlink_file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_file&quot;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_file&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		real_dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target/inner.txt&quot;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		symlink_dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_dir&quot;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_dir/inner.txt&quot;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_dir&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;esac&lt;/span&gt;

	&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; .gitignore
	&lt;span class=&quot;token assign-left variable&quot;&gt;result_no_slash&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;check_ignored &lt;span class=&quot;token string&quot;&gt;&quot;^target($|/)&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

	&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; .gitignore
	&lt;span class=&quot;token assign-left variable&quot;&gt;result_with_slash&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;check_ignored &lt;span class=&quot;token string&quot;&gt;&quot;^target($|/)&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

	&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;| &lt;span class=&quot;token variable&quot;&gt;$2&lt;/span&gt; | &lt;span class=&quot;token variable&quot;&gt;${result_no_slash}&lt;/span&gt; | &lt;span class=&quot;token variable&quot;&gt;${result_with_slash}&lt;/span&gt; |&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;| Type | &#92;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt; | &#92;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;target/&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt; |&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;|---|---|---|&quot;&lt;/span&gt;
run_test &lt;span class=&quot;token string&quot;&gt;&quot;real_file&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;File&quot;&lt;/span&gt;
run_test &lt;span class=&quot;token string&quot;&gt;&quot;symlink_file&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Symbolic Link → File&quot;&lt;/span&gt;
run_test &lt;span class=&quot;token string&quot;&gt;&quot;real_dir&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Directory&quot;&lt;/span&gt;
run_test &lt;span class=&quot;token string&quot;&gt;&quot;symlink_dir&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Symbolic Link → Directory&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h2&gt;Prefix&lt;/h2&gt;
&lt;p&gt;From the results we can confirm that Git does not prefix match. A pattern of target will not match target-extra, so omitting the trailing &lt;code&gt;/&lt;/code&gt; is safe.&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto;&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;&lt;code&gt;target&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;target/&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbolic Link → File&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Directory&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbolic Link → Directory&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;td&gt;📦&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;details&gt;
&lt;summary&gt;gitignore-prefix-test.sh&lt;/summary&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; errexit
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; nounset

&lt;span class=&quot;token comment&quot;&gt;# Emoji output: 🚫 = ignored, 📦 = tracked&lt;/span&gt;
&lt;span class=&quot;token function-name function&quot;&gt;check_ignored&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; ls-files &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; --exclude-standard &lt;span class=&quot;token parameter variable&quot;&gt;--others&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-E&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;📦&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;🚫&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function-name function&quot;&gt;run_test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token assign-left variable&quot;&gt;test_dir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;mktemp &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${test_dir}&lt;/span&gt;&quot;&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; init &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target-extra&quot;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		symlink_file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_file&quot;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_file&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target-extra&quot;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target-extra&quot;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target-extra/inner.txt&quot;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		symlink_dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_dir&quot;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_dir/inner.txt&quot;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;source_dir&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target-extra&quot;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;esac&lt;/span&gt;

	&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; .gitignore
	&lt;span class=&quot;token assign-left variable&quot;&gt;result_no_slash&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;check_ignored &lt;span class=&quot;token string&quot;&gt;&quot;^target-extra($|/)&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

	&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; .gitignore
	&lt;span class=&quot;token assign-left variable&quot;&gt;result_with_slash&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;check_ignored &lt;span class=&quot;token string&quot;&gt;&quot;^target-extra($|/)&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

	&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;| &lt;span class=&quot;token variable&quot;&gt;$2&lt;/span&gt; | &lt;span class=&quot;token variable&quot;&gt;${result_no_slash}&lt;/span&gt; | &lt;span class=&quot;token variable&quot;&gt;${result_with_slash}&lt;/span&gt; |&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;| Type | &#92;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt; | &#92;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;target/&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt; |&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;|---|---|---|&quot;&lt;/span&gt;
run_test &lt;span class=&quot;token string&quot;&gt;&quot;file&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;File&quot;&lt;/span&gt;
run_test &lt;span class=&quot;token string&quot;&gt;&quot;symlink_file&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Symbolic Link → File&quot;&lt;/span&gt;
run_test &lt;span class=&quot;token string&quot;&gt;&quot;dir&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Directory&quot;&lt;/span&gt;
run_test &lt;span class=&quot;token string&quot;&gt;&quot;symlink_dir&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Symbolic Link → Directory&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;style&gt;
	table {
		width: 60%;
	}
	td, th {
		padding: 8px 16px !important;
	}
	details summary {
	  cursor: pointer;
	  padding: 12px 16px;
	  margin: 10px 0 0 0;
	  font-family: monospace;
	  background-color: #2b2b2b;
	  color: #d4d4d4;
	  border-radius: 4px 4px 0 0;
	  user-select: none;
	}
	details summary:hover {
	  background-color: #3a3a3a;
	}
	details[open] summary {
	  border-radius: 4px 4px 0 0;
	}
	details pre {
	  margin-top: 0;
	  border-radius: 0 0 4px 4px;
	}
&lt;/style&gt;
</content>
  </entry>
</feed>
