mirror of
https://github.com/jbranchaud/til
synced 2026-09-04 02:31:46 +00:00
Compare commits
2
Commits
master
..
197bd95dc4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
197bd95dc4 | ||
|
|
295fe153ad |
@@ -1,5 +0,0 @@
|
|||||||
[submodule "notes"]
|
|
||||||
path = notes
|
|
||||||
url = git@github.com:jbranchaud/til-notes-private.git
|
|
||||||
branch = main
|
|
||||||
ignore = all
|
|
||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
vars:
|
|
||||||
NOTES_DIR: notes
|
|
||||||
NOTES_FILE: '{{.NOTES_DIR}}/NOTES.md'
|
|
||||||
EDITOR: '{{.EDITOR | default "nvim"}}'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
desc: Show available commands
|
|
||||||
cmds:
|
|
||||||
- task --list
|
|
||||||
|
|
||||||
browse:list:
|
|
||||||
desc: Print deduped, newest-first TIL paths
|
|
||||||
silent: true
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
git log --diff-filter=A --name-only --pretty=format: -- '*/*.md' \
|
|
||||||
| grep -v '^$' \
|
|
||||||
| awk '!seen[$0]++'
|
|
||||||
|
|
||||||
browse:
|
|
||||||
desc: Pick from 5 most recent TILs (fzf) and open in browser
|
|
||||||
interactive: true
|
|
||||||
silent: true
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
LIST=$(task browse:list)
|
|
||||||
FILE=$(printf '%s\n' "$LIST" | head -5 | fzf --prompt="Open TIL: " --height=40% --reverse) || true
|
|
||||||
if [ -n "$FILE" ]; then
|
|
||||||
gh browse "$FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
browse:latest:
|
|
||||||
desc: Open the single most recent TIL in the browser
|
|
||||||
silent: true
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
FILE=$(task browse:list | awk 'NR==1')
|
|
||||||
if [ -n "$FILE" ]; then
|
|
||||||
gh browse "$FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
notes:
|
|
||||||
desc: Interactive picker for notes tasks
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
TASK=$(task --list | grep "^\* notes:" | sed 's/^\* notes://' | sed 's/\s\+/ - /' | fzf --prompt="Select notes task: " --height=40% --reverse) || true
|
|
||||||
if [ -n "$TASK" ]; then
|
|
||||||
TASK_NAME=$(echo "$TASK" | awk '{print $1}' | sed 's/:$//')
|
|
||||||
task notes:$TASK_NAME
|
|
||||||
fi
|
|
||||||
interactive: true
|
|
||||||
silent: true
|
|
||||||
|
|
||||||
notes:edit:
|
|
||||||
desc: All-in-one edit, commit, and push notes
|
|
||||||
cmds:
|
|
||||||
- task notes:open
|
|
||||||
- task notes:push
|
|
||||||
|
|
||||||
notes:sync:
|
|
||||||
desc: Sync latest changes from the notes submodule
|
|
||||||
cmds:
|
|
||||||
- cd {{.NOTES_DIR}} && git checkout main && git pull
|
|
||||||
silent: false
|
|
||||||
|
|
||||||
notes:open:
|
|
||||||
desc: Opens NOTES.md (syncs latest changes first) in default editor
|
|
||||||
deps: [notes:sync]
|
|
||||||
cmds:
|
|
||||||
- $EDITOR {{.NOTES_FILE}}
|
|
||||||
interactive: true
|
|
||||||
|
|
||||||
notes:push:
|
|
||||||
desc: Commit and push changes to notes submodule
|
|
||||||
dir: '{{.NOTES_DIR}}'
|
|
||||||
cmds:
|
|
||||||
- git add NOTES.md
|
|
||||||
- git commit -m "Update notes - $(date '+%Y-%m-%d %H:%M')"
|
|
||||||
- git pull --rebase
|
|
||||||
- git push
|
|
||||||
status:
|
|
||||||
- git diff --exit-code NOTES.md
|
|
||||||
silent: false
|
|
||||||
|
|
||||||
notes:status:
|
|
||||||
desc: Check status of notes submodule
|
|
||||||
dir: '{{.NOTES_DIR}}'
|
|
||||||
cmds:
|
|
||||||
- git status
|
|
||||||
|
|
||||||
notes:diff:
|
|
||||||
desc: Show uncommitted changes in notes
|
|
||||||
dir: '{{.NOTES_DIR}}'
|
|
||||||
cmds:
|
|
||||||
- git diff NOTES.md
|
|
||||||
|
|
||||||
notes:log:
|
|
||||||
desc: Show recent commit history for notes
|
|
||||||
dir: '{{.NOTES_DIR}}'
|
|
||||||
cmds:
|
|
||||||
- git log --oneline -10
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# Find And Follow Server Logs
|
|
||||||
|
|
||||||
Let's say you are authenticated with the AWS CLI and have the appropriate
|
|
||||||
CloudWatch permissions. You have a few services running in production with
|
|
||||||
associated logs. One of those is a Rails server.
|
|
||||||
|
|
||||||
We want to run `aws logs tail`, but first we check how that command works.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws logs tail help
|
|
||||||
```
|
|
||||||
|
|
||||||
We see a bunch of options, but the only required one is `group_name` ("The name
|
|
||||||
of the CloudWatch Logs group."). We may also notice the `--follow` flag which
|
|
||||||
we'll want to use as well to keep incoming logs flowing.
|
|
||||||
|
|
||||||
We need to determine the log group name for the Rails server. We can do that
|
|
||||||
from the CLI as well (no need to dig into the web UI).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws logs describe-log-groups
|
|
||||||
|
|
||||||
{
|
|
||||||
"logGroups": [
|
|
||||||
{
|
|
||||||
"logGroupName": "/aws/codebuild/fc-rails-app-abcefg-123456",
|
|
||||||
"creationTime": 1739476650823,
|
|
||||||
"metricFilterCount": 0,
|
|
||||||
"arn": "arn:aws:logs:us-east-2:123456789:log-group:/aws/codebuild/fc-rails-app-abcefg-123456:*",
|
|
||||||
"storedBytes": 65617,
|
|
||||||
"logGroupClass": "STANDARD",
|
|
||||||
"logGroupArn": "arn:aws:logs:us-east-2:123456789:log-group:/aws/codebuild/fc-rails-app-abcefg-123456"
|
|
||||||
},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Because the group name is descriptive enough, we can find the log group we are
|
|
||||||
interested in: `/aws/codebuild/fc-rails-app-abcefg-123456`.
|
|
||||||
|
|
||||||
Now we know what we want to `tail`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws logs tail /aws/codebuild/fc-rails-app-abcefg-123456 --follow
|
|
||||||
```
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# List RDS Snapshots With Matching Identifier Prefix
|
|
||||||
|
|
||||||
I'm working on a script that manually creates a snapshot which it will then
|
|
||||||
restore to a temporary database that I can scrub and dump. The snapshots that
|
|
||||||
this script takes are _manual_ and they are named with identifiers that have a
|
|
||||||
defining prefix (`dev-snapshot-`). Besides the few snapshots created by this
|
|
||||||
script, there are tons of automated snapshots that RDS creates for
|
|
||||||
backup/recovery purposes.
|
|
||||||
|
|
||||||
I want to list any snapshots that have been created by the script. I can do
|
|
||||||
this with the `describe-db-snapshots` command and some filters.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws rds describe-db-snapshots \
|
|
||||||
--snapshot-type manual \
|
|
||||||
--query "DBSnapshots[?starts_with(DBSnapshotIdentifier, 'dev-snapshot-')].DBSnapshotIdentifier" \
|
|
||||||
--no-cli-pager
|
|
||||||
|
|
||||||
[
|
|
||||||
"dev-snapshot-20250327-155355"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
There are two key pieces. The `--snapshot-type manual` filter excludes all
|
|
||||||
those automated snapshots. The `--query` both filters to any snapshots whose
|
|
||||||
identifier `?starts_with` the prefix `dev-snapshot-` and then refines the
|
|
||||||
output to just the `DBSnapshotIdentifier` instead of the entire JSON object.
|
|
||||||
|
|
||||||
[source](https://docs.aws.amazon.com/cli/latest/reference/rds/describe-db-snapshots.html)
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Output CLI Results In Different Formats
|
|
||||||
|
|
||||||
The AWS CLI can output the results of commands in three different formats.
|
|
||||||
|
|
||||||
- Text
|
|
||||||
- JSON
|
|
||||||
- Table
|
|
||||||
|
|
||||||
The _default_ output format for my AWS CLI is currently configured to `json`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws configure get output
|
|
||||||
json
|
|
||||||
```
|
|
||||||
|
|
||||||
I can either accept the default or I can override it with the `--output` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws rds describe-db-instances \
|
|
||||||
--query 'DBInstances[*].Endpoint' \
|
|
||||||
--no-cli-pager
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"Address": "fc-database-abcefg-ab1c23de.asdfgh4zxcvb.us-east-2.rds.amazonaws.com",
|
|
||||||
"Port": 5432,
|
|
||||||
"HostedZoneId": "A1BCDE2FG345H6"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
$ aws rds describe-db-instances \
|
|
||||||
--query 'DBInstances[*].Endpoint' \
|
|
||||||
--no-cli-pager \
|
|
||||||
--output table
|
|
||||||
----------------------------------------------------------------------------------------------------
|
|
||||||
| DescribeDBInstances |
|
|
||||||
+-----------------------------------------------------------------------+-----------------+--------+
|
|
||||||
| Address | HostedZoneId | Port |
|
|
||||||
+-----------------------------------------------------------------------+-----------------+--------+
|
|
||||||
| fc-database-abcefg-ab1c23de.asdfgh4zxcvb.us-east-2.rds.amazonaws.com | A1BCDE2FG345H6 | 5432 |
|
|
||||||
+-----------------------------------------------------------------------+-----------------+--------+
|
|
||||||
|
|
||||||
$ aws rds describe-db-instances \
|
|
||||||
--query 'DBInstances[*].Endpoint' \
|
|
||||||
--no-cli-pager \
|
|
||||||
--output text
|
|
||||||
fc-database-abcefg-ab1c23de.asdfgh4zxcvb.us-east-2.rds.amazonaws.com A1BCDE2FG345H6 5432
|
|
||||||
```
|
|
||||||
|
|
||||||
[source](https://docs.aws.amazon.com/cli/v1/userguide/cli-usage-output-format.html)
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# SSH Into An ECS Container
|
|
||||||
|
|
||||||
In [Connect To Production Rails Console on AWS /
|
|
||||||
Flightcontrol](https://www.visualmode.dev/connect-to-production-rails-console-aws-flightcontrol),
|
|
||||||
I went into full detail about how to access `rails console` for a production
|
|
||||||
Rails app running in an ECS container.
|
|
||||||
|
|
||||||
A big part of that process was establishing an SSH connection to the ECS container.
|
|
||||||
|
|
||||||
To do that, I need to know my region, container ID, and task ID. I can get the
|
|
||||||
first two by listing my clusters and finding the cluster/container that houses
|
|
||||||
the Rails app.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws ecs list-clusters
|
|
||||||
{
|
|
||||||
"clusterArns": [
|
|
||||||
"arn:aws:ecs:us-east-2:123:cluster/rails-app-abc123"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The region then is `us-east-2` and the container ID is `rails-app-abc123`.
|
|
||||||
|
|
||||||
I can use that to find the task ID:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws ecs list-tasks --region us-east-2 --cluster rails-app-abc123
|
|
||||||
{
|
|
||||||
"taskArns": [
|
|
||||||
"arn:aws:ecs:us-east-2:123:task/rails-app-abc123/8526b3191d103bb1ff90c65a655ad004"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The task ID is the final portion of the URL:
|
|
||||||
`8526b3191d103bb1ff90c65a655ad004`.
|
|
||||||
|
|
||||||
Putting this all together I can SSH into the ECS container with a bash profile
|
|
||||||
like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws ecs execute-command \
|
|
||||||
--region us-east-2 \
|
|
||||||
--cluster rails-app-abc123 \
|
|
||||||
--container rails-app-abc123 \
|
|
||||||
--task 8526b3191d103bb1ff90c65a655ad004 \
|
|
||||||
--interactive \
|
|
||||||
--command "/bin/bash"
|
|
||||||
```
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Turn Off Output Pager For A Command
|
|
||||||
|
|
||||||
It is not uncommon for an AWS CLI command to return a ton of output. When that
|
|
||||||
happens, it is nice that the results end up in pager program (like `less`)
|
|
||||||
where you can search and review them, copy a value of interest, and then exit.
|
|
||||||
The pager prevents that wall of output from cluttering your terminal history.
|
|
||||||
|
|
||||||
However, sometimes I am running a command that I know is going to return a
|
|
||||||
small result. I'd rather have the results go to stdout where I can see them in
|
|
||||||
the terminal history rather than to an ephemeral pager.
|
|
||||||
|
|
||||||
For that situation I can tack on the `--no-cli-pager` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws rds describe-db-instances \
|
|
||||||
--query 'DBInstances[*].EngineVersion' \
|
|
||||||
--output json \
|
|
||||||
--no-cli-pager
|
|
||||||
|
|
||||||
[
|
|
||||||
"13.15",
|
|
||||||
"16.8"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Here I've asked the AWS CLI to tell me the engine versions of all my RDS
|
|
||||||
Postgres databases. Because I know the results are only going to include a
|
|
||||||
couple results for my couple of DBs, I'd like to skip the pager —
|
|
||||||
`--no-cli-pager`.
|
|
||||||
|
|
||||||
Though I think it is better to do this on a case by case basis, it is also
|
|
||||||
possible to turn off the pager via the CLI configuration file.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws configure set cli_pager ""
|
|
||||||
```
|
|
||||||
|
|
||||||
[source](https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-pagination.html#cli-usage-pagination-clientside)
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Use Specific AWS Profile With CLI
|
|
||||||
|
|
||||||
I have multiple AWS profiles authenticated with the AWS CLI. For some projects
|
|
||||||
I need to use the `default` one and for others I need to use the other.
|
|
||||||
|
|
||||||
First, I can list the available profiles like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws configure list-profiles
|
|
||||||
default
|
|
||||||
dev-my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
For one-off commands I can specify the profile for any AWS CLI command using
|
|
||||||
the `--profile` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ aws ecs list-clusters --profile josh-visualmode
|
|
||||||
```
|
|
||||||
|
|
||||||
However, I don't want to have to specify that flag every time when I'm working
|
|
||||||
on a specific project. Instead I can specify the profile with an environment
|
|
||||||
variable. The [`direnv`](https://direnv.net/) tool is a great way to do this on
|
|
||||||
a per-project / per-directory basis.
|
|
||||||
|
|
||||||
I can create or update the `.envrc` file (assuming I have `direnv` installed)
|
|
||||||
adding the following line (and re-allowing the changed file):
|
|
||||||
|
|
||||||
```
|
|
||||||
# .envrc
|
|
||||||
export AWS_PROFILE=dev-my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, any AWS command I issue from that directory or its subdirectories will use
|
|
||||||
that profile by default.
|
|
||||||
|
|
||||||
[source](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html#cli-configure-files-using-profiles)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Edit The Current Command Prompt
|
|
||||||
|
|
||||||
A neat feature of `bash` is the ability to open whatever the current state of
|
|
||||||
the command prompt is into your default editor.
|
|
||||||
|
|
||||||
Let's say we have a really long command that we've just tried to run, but it
|
|
||||||
failed and we need to make a small change somewhere in the middle. Instead of
|
|
||||||
holding the left arrow key for 30 seconds, we can instead hit `CTRL-X CTRL-E`.
|
|
||||||
|
|
||||||
This pops us into our `EDITOR` (or maybe `VISUAL`, not sure which). In my case,
|
|
||||||
that is `nvim`. I now have access to all the features I'm used to in `nvim` for
|
|
||||||
quickly navigating to and editing, searching and replacing, or whatever.
|
|
||||||
|
|
||||||
Once I've got the command how I like it, I can save and exit (`:wq`) and the
|
|
||||||
updated command will be executed.
|
|
||||||
|
|
||||||
This is similar to [the `fc` builtin](unix/fix-previous-command-with-fc.md),
|
|
||||||
which also happens to be available for `zsh`.
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Clean Up Your Brew Installations
|
|
||||||
|
|
||||||
Over time as you upgrade brew-installed programs and make changes to your
|
|
||||||
`Brewfile`, your machine will have artifacts left behind that you no longer
|
|
||||||
need.
|
|
||||||
|
|
||||||
Periodically, it is good to clean things up.
|
|
||||||
|
|
||||||
First, you can get a summary of stale and outdated files that brew has
|
|
||||||
installed. Use the `--dry-run` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ brew cleanup --dry-run
|
|
||||||
```
|
|
||||||
|
|
||||||
If you feel good about what you see in the output, then give things a clean.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ brew cleanup
|
|
||||||
```
|
|
||||||
|
|
||||||
Second, if you are using a `Brewfile` to manage what `brew` installs, then you
|
|
||||||
can instruct `brew` to uninstall any dependencies that aren't specified in that
|
|
||||||
file.
|
|
||||||
|
|
||||||
By default it operates as a dry run and the `--force` flag will be needed to
|
|
||||||
actually do the cleanup. And specify the filename if it doesn't match the
|
|
||||||
default of `Brewfile`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ brew bundle cleanup --file=Brewfile.personal
|
|
||||||
```
|
|
||||||
|
|
||||||
If the output looks good, then force the cleanup:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ brew bundle cleanup --force --file=Brewfile.personal
|
|
||||||
```
|
|
||||||
|
|
||||||
See `brew cleanup --help` and `brew bundle --help` for more details.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Install From Nonstandard Brewfile
|
|
||||||
|
|
||||||
When you want to install the packages listed in the `Brewfile` for your current
|
|
||||||
project (or dotfiles), you can run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ brew bundle
|
|
||||||
```
|
|
||||||
|
|
||||||
And `brew` knows to look for and use the `Brewfile` in the current directory.
|
|
||||||
|
|
||||||
If, however, you are trying to run `brew bundle` for a `Brewfile` located
|
|
||||||
somewhere besides the current directory *OR* you want to target a file with a
|
|
||||||
non-standard name (like
|
|
||||||
[`Brewfile.personal`](https://github.com/jbranchaud/dotfiles/blob/main/Brewfile.personal)),
|
|
||||||
then you can use the `--file` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ brew bundle --file Brewfile.personal
|
|
||||||
```
|
|
||||||
|
|
||||||
This is what I do [here in my `dotfiles`
|
|
||||||
repo](https://github.com/jbranchaud/dotfiles/blob/b053f6251cae7ed52f698fc2a2c40ba82c5881b0/installer/mac-setup.sh#L42-L48).
|
|
||||||
|
|
||||||
See `man brew` and find the section on `brew bundle` for more details.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Install Go Packages In Brewfile
|
|
||||||
|
|
||||||
Typically my `Brewfile` is only full of `brew` and `cask` directives. That's
|
|
||||||
starting to change now that `brew` supports installing Go packages listed in the
|
|
||||||
`Brewfile`.
|
|
||||||
|
|
||||||
Use the `go` directive and the URL to the hosted Go package.
|
|
||||||
|
|
||||||
Here is an example of a `Brewfile` that includes a `cask`, `brew`, and `go`
|
|
||||||
directive.
|
|
||||||
|
|
||||||
```
|
|
||||||
# screen resolution tool
|
|
||||||
cask "betterdisplay"
|
|
||||||
|
|
||||||
# Mac keychain management, gpg key
|
|
||||||
brew "pinentry-mac"
|
|
||||||
|
|
||||||
# Sanitized production Postgres dumps
|
|
||||||
go "github.com/jackc/pg_partialcopy"
|
|
||||||
```
|
|
||||||
|
|
||||||
I've recently added the exact package from above to my [`dotfiles`
|
|
||||||
repo](https://github.com/jbranchaud/dotfiles/commit/e83e9d19504f0e2f95eba33123f907f999bf865e).
|
|
||||||
|
|
||||||
Here is the [PR to `brew`](https://github.com/Homebrew/brew/pull/20798) where
|
|
||||||
this functionality was added back in October of 2025.
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Duplicate Current Browser Tab
|
|
||||||
|
|
||||||
Let's say I'm on a specific page within a web app. Maybe I'm typing out a
|
|
||||||
comment on that page. Before I can hit submit, I need to reference something
|
|
||||||
elsewhere in the app, perhaps on the previous page.
|
|
||||||
|
|
||||||
I can `cmd+t` to open a new tab, type out the URL, hit enter, and then navigate
|
|
||||||
around until I find the page I'm looking for. I do this exact thing sometimes,
|
|
||||||
but it feels slow and clunky.
|
|
||||||
|
|
||||||
The other way I accomplish this which feels way smoother is to _duplicate the
|
|
||||||
current tab_. I can do that by `cmd`-clicking the reload button next to the URL
|
|
||||||
bar.
|
|
||||||
|
|
||||||
This opens another tab at the same URL with the same navigation history. I can
|
|
||||||
switch to that tab and hit the back button to go to the previous page, find the
|
|
||||||
thing I wanted to reference, and then return to the previous tab to finish what
|
|
||||||
I was doing.
|
|
||||||
|
|
||||||
To give a more concrete example: this happens all the time with GitHub PR
|
|
||||||
comments where I want to copy the URL or number of a specific PR to reference in
|
|
||||||
a comment I'm leaving on the current PR. Duplicating the tab, going _back_ to
|
|
||||||
the PR index view, finding the other PR, and copying its URL -- to me that is a
|
|
||||||
quicker flow popping open a fresh browser tab.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Open Current Tab In New Window With Vimium
|
|
||||||
|
|
||||||
Sometime I have a busy Chrome window going with a bunch of tabs open for
|
|
||||||
various lines of work as well as a number of tabs that I've neglected to close.
|
|
||||||
I then open a new tab, find something useful, and realize I'm at a "branching
|
|
||||||
point". I'm about to start in on a specific chunk of work that will probably
|
|
||||||
involve opening several more tabs and switch back and forth between some
|
|
||||||
dashboards. I want to start all of this from a fresh slate -- or at least from
|
|
||||||
a fresh Chrome window.
|
|
||||||
|
|
||||||
With [Vimium](https://github.com/philc/vimium), I can hit `W` (`Shift-w`) to
|
|
||||||
have the current tab move from the current window to a new window. The original
|
|
||||||
window, minus that one tab, will be left as is so that I can go back to it as
|
|
||||||
needed.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Search Tabs With The Vimium Vomnibar
|
|
||||||
|
|
||||||
If you use Chrome like I do, then you eventually end up with several windows
|
|
||||||
with dozens if not 100+ tabs open. It can start to get tedius with that many
|
|
||||||
tabs to find and navigate to a given tab. Someone might suggest closing a few
|
|
||||||
dozen tabs as a solution to this predicament. However, Vimium offers a solution
|
|
||||||
that doesn't require I [_kill my
|
|
||||||
darlings_](https://en.wiktionary.org/wiki/kill_one%27s_darlings).
|
|
||||||
|
|
||||||
The Vomnibar, a Vimium-powered search bar, can be summoned with `T` to only
|
|
||||||
search through open tabs.
|
|
||||||
|
|
||||||
When I hit `T`, I see a text area (for refining the search) and then a bunch of
|
|
||||||
entries populate below that which I immediately recognize as many of those tabs
|
|
||||||
that I'm going to get back to one of these days.
|
|
||||||
|
|
||||||
To narrow down to the specific thing I'm looking for, I type something into the
|
|
||||||
input. Then I arrow to the result I'm looking for and hit enter. And I'm
|
|
||||||
transported to that tab.
|
|
||||||
|
|
||||||
If I don't like where I ended up, I can also go back to the tab I had been on
|
|
||||||
with `^`.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Allow Edits From The Start
|
|
||||||
|
|
||||||
A common pattern for me when using Claude Code is that I start it up in a
|
|
||||||
project, I prompt it with a question or feature spec, it either comes up with a
|
|
||||||
plan or just starts working, and as soon as it is ready to make its first edits
|
|
||||||
to a file, it prompts me something like:
|
|
||||||
|
|
||||||
```
|
|
||||||
Do you want to make this edit to Taskfile.yml?
|
|
||||||
❯ 1. Yes
|
|
||||||
2. Yes, allow all edits during this session (shift+tab)
|
|
||||||
3. Type here to tell Claude what to do differently
|
|
||||||
```
|
|
||||||
|
|
||||||
That's a nice default so that I don't get surprised by Claude Code editing a
|
|
||||||
bunch of files.
|
|
||||||
|
|
||||||
However, if I'm in a git-backed project and I'm going into a session intending
|
|
||||||
to make edits, then I can skip the formalities. I can tell Claude Code when
|
|
||||||
starting up the session that edits are allowed.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ claude --permission-mode acceptEdits
|
|
||||||
```
|
|
||||||
|
|
||||||
When I do this, I'll see the following indicator below the prompt input field:
|
|
||||||
|
|
||||||
```
|
|
||||||
⏵⏵ accept edits on (shift+tab to cycle)
|
|
||||||
```
|
|
||||||
|
|
||||||
If I've already started `claude` but I forgot to specify that permission mode, I
|
|
||||||
can also toggle right into _accept edits_ by hitting `Shift+Tab`.
|
|
||||||
|
|
||||||
[source](https://www.youtube.com/watch?v=_IK18goX4X8)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Distinguish Sessions With Different Colors
|
|
||||||
|
|
||||||
I sometimes have several Claude Code sessions open at once. As I bounce between
|
|
||||||
tmux windows, it can sometimes be tricky to tell them apart at a glance. One way
|
|
||||||
that Claude Code can help with this is with some light styling. You can change
|
|
||||||
the accent color of a session with the `/color` command.
|
|
||||||
|
|
||||||
Run it as is and it will choose a random color to set the session to.
|
|
||||||
|
|
||||||
Or you can pick from any of the available colors which it will give you a hint
|
|
||||||
for if you type a space after `/color`.
|
|
||||||
|
|
||||||
```
|
|
||||||
/color [red|blue|green|yellow|purple|orange|pink|cyan|default]
|
|
||||||
```
|
|
||||||
|
|
||||||
I can run the following to set it to cyan:
|
|
||||||
|
|
||||||
```
|
|
||||||
/color cyan
|
|
||||||
```
|
|
||||||
|
|
||||||
More details on this kinds of commands can be found in the [_Commands_
|
|
||||||
docs](https://code.claude.com/docs/en/commands).
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Monitor Usage Limits From CLI
|
|
||||||
|
|
||||||
When I first started using Claude Code enough to push the usage limits, I would
|
|
||||||
periodically switch over to the browser to check
|
|
||||||
`https://claude.ai/settings/usage` to see how close I was getting. That page
|
|
||||||
would tell me what percentage of my allotted usage I had consumed so far for the
|
|
||||||
current 5-hour session and then how long until that 5-hour usage window resets.
|
|
||||||
|
|
||||||
This can also be viewed directly in Claude Code for the CLI.
|
|
||||||
|
|
||||||
First, run the `/status` slash command and then _tab_ over to the _Usage_
|
|
||||||
section. There you will see the same details as in the web view.
|
|
||||||
|
|
||||||
I'm also learned, as I write this, that you can go directly to the _Usage_
|
|
||||||
section by typing the `/usage` slash command.
|
|
||||||
|
|
||||||
See [the docs](https://code.claude.com/docs/en/slash-commands) for a listing of
|
|
||||||
all slash commands.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Open Current Prompt In Default Editor
|
|
||||||
|
|
||||||
[Claude Code](https://www.claude.com/product/claude-code) gives you a single
|
|
||||||
line to write a prompt. You can write and write as much as you want, but it will
|
|
||||||
all be on that single line. And avoid accidentally hitting 'Enter' before you're
|
|
||||||
done.
|
|
||||||
|
|
||||||
I found myself wanting to space out my thoughts, create a code block as part of
|
|
||||||
a prompt, and generally have a scratch pad instead of just a text box. By
|
|
||||||
hitting `ctrl-g`, I can move the current prompt into my default editor (in my
|
|
||||||
case, `nvim`). From there I can continue to write, edit, and format with all the
|
|
||||||
affordances of an editor.
|
|
||||||
|
|
||||||
Once I'm done crafting the prompt, I can save (e.g. `:wq`) and Claude Code will
|
|
||||||
be primed with that text. I can then hit 'Enter' to let `claude` do its thing.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Resume Specific Session
|
|
||||||
|
|
||||||
There are a few different ways to resume a [Claude
|
|
||||||
Code](https://code.claude.com/docs/en/overview) session.
|
|
||||||
|
|
||||||
First, if I have exited a session for the current project and I want to pick
|
|
||||||
back up with that most recent one, then I can use `claude --continue`.
|
|
||||||
|
|
||||||
If I have had a few recent sessions for the current project and I want to
|
|
||||||
remember what they were and pick up where I left off with one of them, then I
|
|
||||||
can use `claude --resume` (with no argument). That will open a picker where I
|
|
||||||
can browser through a summary of the recent sessions based on their starting
|
|
||||||
prompt. The one I pick is the session that will be resumed.
|
|
||||||
|
|
||||||
Finally, if I have grabbed a specific session ID (UUID) during the session from
|
|
||||||
the `/status` output, then I can reference that value directly.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ claude --resume 92170532-be31-4a91-b2a9-025b8fa78232
|
|
||||||
```
|
|
||||||
|
|
||||||
See `claude --help` for more details.
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Set Permission Mode When Starting Session
|
|
||||||
|
|
||||||
The way I typically use Claude Code day-to-day is with a couple long-running
|
|
||||||
sessions for one to two clones of the project. I start a session with `claude`
|
|
||||||
and then hit `shift+tab` until I've toggled it to _auto_ mode. I do tightly
|
|
||||||
scoped features and `/clear` the context in between each.
|
|
||||||
|
|
||||||
I get used to being in _auto_ mode, so whenever I start a new `claude` session I
|
|
||||||
forget to first toggle from _manual_ to _auto_ mode.
|
|
||||||
|
|
||||||
This is where the
|
|
||||||
[`--permission-mode`](https://code.claude.com/docs/en/permission-modes) flag can
|
|
||||||
help. I can start a session directly in _auto_ mode like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ claude --permission-mode auto
|
|
||||||
```
|
|
||||||
|
|
||||||
Or if I know I want to generate a plan first, I can start it in _plan_ mode.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ claude --permission-mode plan
|
|
||||||
```
|
|
||||||
|
|
||||||
There is also the `--dangerously-skip-permissions` flag which is equivalent to
|
|
||||||
`--permission-mode bypassPermissions`. I tend to stay away from those unless I'm
|
|
||||||
working from a sandboxed dev container.
|
|
||||||
|
|
||||||
See `claude --help` for more details.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Stash The Current Prompt To Send Another First
|
|
||||||
|
|
||||||
I've been working my way through the current cohort of Matt Pocock's [Claude
|
|
||||||
Code for Real
|
|
||||||
Engineers](https://www.aihero.dev/cohorts/claude-code-for-real-engineers-2026-04).
|
|
||||||
The best part about going through a series of videos like this is being able to
|
|
||||||
pick up big and small tips and tricks from another person's workflow.
|
|
||||||
|
|
||||||
One of the small things I picked up in an early video is the ability to stash
|
|
||||||
the current prompt.
|
|
||||||
|
|
||||||
Let's say I've gone to the trouble of writing out a detailed prompt, `@`'ing
|
|
||||||
some files, and so forth. Then I realize I need first prompt Claude to do
|
|
||||||
something else first. Instead of copy-pasting that prompt into my notes,
|
|
||||||
deleting it, issuing a different prompt, and then pasting it back in, I can hit
|
|
||||||
`Ctrl-s`.
|
|
||||||
|
|
||||||
`Ctrl-s` will _stash_ the current prompt, clearing out the prompt input. I can
|
|
||||||
then type in something else. Once I hit enter for that new prompt, it will be
|
|
||||||
sent to Claude and the stashed prompt will be immediately populated back into
|
|
||||||
the input.
|
|
||||||
|
|
||||||
Though `Ctrl-s` is mentioned when you hit `?` from within `claude` session, I
|
|
||||||
don't see it documented anywhere in their [Interactive Mode
|
|
||||||
reference](https://code.claude.com/docs/en/interactive-mode).
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Filter Blur Requires Expensive Calculation
|
|
||||||
|
|
||||||
I had [a
|
|
||||||
page](https://www.visualmode.dev/connect-to-production-rails-console-aws-flightcontrol)
|
|
||||||
on my blog that was experiencing some odd rendering behavior. The issue was
|
|
||||||
manifesting a couple ways.
|
|
||||||
|
|
||||||
- Resizing and scrolling were janky and causing entire page layers to re-render
|
|
||||||
causing the page to flash in and out.
|
|
||||||
- Sometimes entire layer chunks would fail to paint leaving a white block
|
|
||||||
missing from the page.
|
|
||||||
|
|
||||||
The issue was occurring with and without JavaScript turned on for a
|
|
||||||
statically-built page. I suspected that some aspect of the CSS was at fault.
|
|
||||||
|
|
||||||
I was going back and forth with Dillon Hafer about what the issue could be and
|
|
||||||
he wondered, "could it be the backdrop-blur class from tailwind?". I tried
|
|
||||||
removing that class and the responsiveness of the page immediately improved.
|
|
||||||
|
|
||||||
The [`filter:
|
|
||||||
blur`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/blur)
|
|
||||||
and [`backdrop-filter:
|
|
||||||
blur`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter) both
|
|
||||||
use an expensive [Gaussian blur](https://en.wikipedia.org/wiki/Gaussian_blur)
|
|
||||||
calculation. One of these on a modern machine and browser probably won't have a
|
|
||||||
noticable impact. However, a bunch of them, as in the case of my page with a
|
|
||||||
recurring component, can have quite the performance hit.
|
|
||||||
|
|
||||||
[source](https://github.com/tailwindlabs/tailwindcss/issues/15256)
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Prevent Invisible Elements From Being Clicked
|
|
||||||
|
|
||||||
I have a nav element that when clicked reveals a custom drop-down menu. It
|
|
||||||
reveals it using CSS transitions and transformations (`opacity` and `scale`).
|
|
||||||
When the nav element is clicked again, the reverse of these transformations is
|
|
||||||
applied to "hide" the menu. This gives a nice visual effect.
|
|
||||||
|
|
||||||
It only makes the menu invisible and doesn't actually make it go away. That
|
|
||||||
means that menu could be invisible, but hovering over the top of a button on
|
|
||||||
the screen. The button cannot be clicked now because the menu is intercepting
|
|
||||||
that [_pointer
|
|
||||||
event_](https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events).
|
|
||||||
|
|
||||||
The fix is to apply CSS (or a class) when the drop-down menu is closed that
|
|
||||||
tells it to ignore _pointer events_.
|
|
||||||
|
|
||||||
```css
|
|
||||||
.pointer-events-none {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is more of less what [the `pointer-events-none` TailwindCSS
|
|
||||||
utility](https://tailwindcss.com/docs/pointer-events) looks like.
|
|
||||||
|
|
||||||
This class is applied by default to the drop-down menu. Then when the nav item
|
|
||||||
is clicked, some JavaScript removes that class at the same moment that the menu
|
|
||||||
is visually appearing. When a menu item is selected or the menu otherwise
|
|
||||||
closed, it transitions away and the `pointer-events-none` class is reapplied.
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Allow Cursor To Be Launched From CLI
|
|
||||||
|
|
||||||
It is nice to be able to open Cursor for a specific project directly from the
|
|
||||||
terminal like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd ~/dev/my/project
|
|
||||||
|
|
||||||
$ cursor .
|
|
||||||
```
|
|
||||||
|
|
||||||
For the `cursor` launcher binary to be available like that, we have to find it
|
|
||||||
and add it to the path.
|
|
||||||
|
|
||||||
It is probably located in the `/Applications` folder and within that nested down
|
|
||||||
a couple directories is a `bin` directory that contains the binary we're looking
|
|
||||||
for.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ls /Applications/Cursor.app/Contents/Resources/app/bin
|
|
||||||
bin/
|
|
||||||
├── code*
|
|
||||||
├── cursor*
|
|
||||||
└── cursor-tunnel*
|
|
||||||
```
|
|
||||||
|
|
||||||
The `cursor` binary is what we want, so let's add that to our path. In my case,
|
|
||||||
I'll add this to my `~/.zshrc` file.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export PATH="/Applications/Cursor.app/Contents/Resources/app/bin:$PATH"
|
|
||||||
```
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Cloudflare Allows CNAME For Apex Domain
|
|
||||||
|
|
||||||
If you want to set up a custom root (apex) domain with an app hosting provider
|
|
||||||
[like
|
|
||||||
Heroku](https://devcenter.heroku.com/articles/custom-domains#add-a-custom-root-domain),
|
|
||||||
you're going to need to work with a DNS provider that supports the non-standard
|
|
||||||
`ALIAS` records (or something equivalent).
|
|
||||||
|
|
||||||
In my case, I have my domain registered with Cloudflare. Cloudflare supports
|
|
||||||
this kind of CNAME lookup of an apex domain through [_CNAME
|
|
||||||
flattening_](https://developers.cloudflare.com/dns/cname-flattening/).
|
|
||||||
|
|
||||||
Unlike other registrars that use a separate `ALIAS` record concept, Cloudflare
|
|
||||||
allows you to set up a specialized `CNAME` record. Go into the DNS settings for
|
|
||||||
the domain of interest, click "Add Record", and then select `CNAME`. From there,
|
|
||||||
instead of entering a traditional subdomain like `www`, you put the `@` symbol
|
|
||||||
which tells Cloudflare that this is a record for the apex domain. That record
|
|
||||||
will still point to a target like `abc123.herokudns.com` as a more traditional
|
|
||||||
`CANME` would do.
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Set, Get, And Unset Env Vars With Dokku
|
|
||||||
|
|
||||||
The `dokku` CLI provides `config` subcommands for managing environment variables
|
|
||||||
for the target container.
|
|
||||||
|
|
||||||
An env var can be set for an active container with `config:set`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dokku config:set app-name JEMALLOC_ENABLED=true MALLOC_CONF="stats_print:true"
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice I'm able to set multiple env vars at once if needed.
|
|
||||||
|
|
||||||
If I ever need to check what an env var is currently set to for one of my app
|
|
||||||
containers, I can use `config:get`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dokku config:get app-name JEMALLOC_ENABLED
|
|
||||||
true
|
|
||||||
```
|
|
||||||
|
|
||||||
I can always override any value with another `config:set`. However, if I need to
|
|
||||||
entirely remove the env var, I can use `config:unset`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dokku config:unset app-name MALLOC_CONF
|
|
||||||
```
|
|
||||||
|
|
||||||
[source](https://dokku.com/docs/configuration/environment-variables/)
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Check Postgres Version Running In Docker Container
|
|
||||||
|
|
||||||
I have a docker container that I'm using to run a PostgreSQL development
|
|
||||||
database on my local machine. It was a while ago when I set it up, so I can't
|
|
||||||
remember specifically which major version of PostgreSQL I am using.
|
|
||||||
|
|
||||||
I use `docker ps` to list the names of each container.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ docker ps --format "{{.Names}}"
|
|
||||||
still-postgres-1
|
|
||||||
better_reads-postgres-1
|
|
||||||
```
|
|
||||||
|
|
||||||
I grab the one I am interested in. In this case, that is `still-postgres-1`.
|
|
||||||
|
|
||||||
Then I can execute a `select version()` statement with `psql` against the
|
|
||||||
container with that name like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ docker exec still-postgres-1 psql -U postgres -c "select version()";
|
|
||||||
version
|
|
||||||
---------------------------------------------------------------------------------------------------------------------
|
|
||||||
PostgreSQL 16.2 (Debian 16.2-1.pgdg120+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
|
|
||||||
(1 row)
|
|
||||||
```
|
|
||||||
|
|
||||||
And there I have it. I'm running Postgres v16 in this container.
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Run SQL Script Against Postgres Container
|
|
||||||
|
|
||||||
I've been using dockerized Postgres for local development with several projects
|
|
||||||
lately. This is typically with framework tooling (like Rails) where schema
|
|
||||||
migrations and query execution are handled by the tooling using the specified
|
|
||||||
connection parameters.
|
|
||||||
|
|
||||||
However, I was experimenting with and iterating on some Postgres functions
|
|
||||||
outside of any framework tooling. I needed a way to run the SQL script that
|
|
||||||
(re)creates the function via `psql` on the docker container.
|
|
||||||
|
|
||||||
With a local, non-containerized Postgres instance, I'd redirect the file to
|
|
||||||
`psql` like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ psql -U postgres -d postgres < experimental-functions.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
When I tried doing this with `docker exec` though, it was silently failing /
|
|
||||||
doing nothing. As far as I can tell, there was a mismatch with redirection
|
|
||||||
handling across the bounds of the container.
|
|
||||||
|
|
||||||
To get around this, I first copy the file into the `/tmp` directory on the
|
|
||||||
container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ docker cp experimental-functions.sql still-postgres-1:/tmp/experimental-functions.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
Then the `psql` command that docker executes can be pointed directly at a
|
|
||||||
local-to-it SQL file.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ docker exec still-postgres-1 psql \
|
|
||||||
-U postgres \
|
|
||||||
-d postgres \
|
|
||||||
-f /tmp/experimental-functions.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
There are probably other ways to handle this, but I got into a nice rhythm with
|
|
||||||
this file full of `create or replace function ...` definitions where I could
|
|
||||||
modify, copy over, execute, run some SQL to verify, and repeat.
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"excludes": ["README.md"],
|
|
||||||
"plugins": ["https://plugins.dprint.dev/markdown-0.16.0.wasm"]
|
|
||||||
}
|
|
||||||
@@ -9,10 +9,10 @@ test runs. Most of these files are tracked (already checked in to the
|
|||||||
repository). There are also many new files generated as part of the most recent
|
repository). There are also many new files generated as part of the most recent
|
||||||
test run.
|
test run.
|
||||||
|
|
||||||
I want to stage the changes to files that are already tracked, but hold off on
|
I want to staging the changes to files that are already tracked, but hold off
|
||||||
doing anything with the new files.
|
on doing anything with the new files.
|
||||||
|
|
||||||
Running `git add spec/cassettes` won't do the trick because that will pull in
|
Running `git add spec/cassettes` won't do the track because that will pull in
|
||||||
everything. Running `git add --patch spec/cassettes` will take long and be
|
everything. Running `git add --patch spec/cassettes` will take long and be
|
||||||
tedious. Instead what I want is the `-u` flag. It's short for _update_ which
|
tedious. Instead what I want is the `-u` flag. It's short for _update_ which
|
||||||
means it will only stage already tracked files.
|
means it will only stage already tracked files.
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
# Check If A File Has Changed In A Script
|
|
||||||
|
|
||||||
If I'm at the command line and I want to check if a file has changed, I can run
|
|
||||||
`git diff` and see what has changed. If I want to be more specific, I can run
|
|
||||||
`git diff README.md` to see if there are changes to that specific file.
|
|
||||||
|
|
||||||
If I'm trying to do this check in a script though, I want the command to clearly
|
|
||||||
tell the script _Yes_ or _No_. Usually a script looks for an exit code to
|
|
||||||
determine what path to take. But as long as `git diff` runs successfully,
|
|
||||||
regardless of whether or not their are changes, it is going to have an
|
|
||||||
affirmative exit code of `0`.
|
|
||||||
|
|
||||||
This is why `git diff` offers the `--exit-code` flag.
|
|
||||||
|
|
||||||
> Make the program exit with codes similar to diff(1). That is, it exits with 1
|
|
||||||
> if there were differences and 0 means no differences.
|
|
||||||
|
|
||||||
With that in mind, we can wire up a script with `git diff` that takes different
|
|
||||||
paths depending on whether or not there are changes.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
if ! git diff --exit-code README.md; then
|
|
||||||
echo "README.md has changes"
|
|
||||||
else
|
|
||||||
echo "README.md is clean"
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
We can take this a step further and instead use the `--quiet` flag.
|
|
||||||
|
|
||||||
> Disable all output of the program. Implies --exit-code. Disables execution of
|
|
||||||
> external diff helpers whose exit code is not trusted
|
|
||||||
|
|
||||||
This exhibits the same behavior as `--exit-code` and goes the additional step of
|
|
||||||
silencing diff output and disabling execution of external diff helpers like
|
|
||||||
`delta`.
|
|
||||||
|
|
||||||
See `man git-diff` for more details.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Check If A File Is Under Version Control
|
|
||||||
|
|
||||||
The `git ls-files` command can be used with the `--error-unmatch` flag to check
|
|
||||||
if a file is under version control. It does this by checking if any of the
|
|
||||||
listed files appears on the _index_. If any does not, it is treated as an error.
|
|
||||||
|
|
||||||
In a project, I have a `README.md` that is under version control. And I have
|
|
||||||
`node_modules` that shouldn't be under version control (which is why they are
|
|
||||||
listed in my `.gitignore` file). I can check the README and a file somewhere in
|
|
||||||
`node_modules`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git ls-files --error-unmatch README.md
|
|
||||||
README.md
|
|
||||||
|
|
||||||
❯ git ls-files --error-unmatch node_modules/@ai-sdk/anthropic/CHANGELOG.md
|
|
||||||
error: pathspec 'node_modules/@ai-sdk/anthropic/CHANGELOG.md' did not match any file(s) known to git
|
|
||||||
Did you forget to 'git add'?
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice the second command results in an error because of the untracked
|
|
||||||
`CHANGELOG.md` file in `node_modules`.
|
|
||||||
|
|
||||||
Here is another example of this at work while specifying multiple files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git ls-files --error-unmatch README.md node_modules/@ai-sdk/anthropic/CHANGELOG.md package.json
|
|
||||||
README.md
|
|
||||||
package.json
|
|
||||||
error: pathspec 'node_modules/@ai-sdk/anthropic/CHANGELOG.md' did not match any file(s) known to git
|
|
||||||
Did you forget to 'git add'?
|
|
||||||
```
|
|
||||||
|
|
||||||
Each tracked file gets listed and then the untracked file results in an error.
|
|
||||||
|
|
||||||
See `man git-ls-files` for more details.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Check What Branches Contain A Specific Commit
|
|
||||||
|
|
||||||
The `git branch` command comes with a `--contains` flag that can tell me what
|
|
||||||
local branches contain a specific commit based on the SHA of that commit.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git branch --contains a73d9173c2399069fa202fe65da0a8927814fd84
|
|
||||||
* main
|
|
||||||
jb/migrate-to-basedpyright
|
|
||||||
jb/migrate-date-files-to-repository-pattern
|
|
||||||
```
|
|
||||||
|
|
||||||
I am currently on the `main` branch which is why it shows the `*` next to that
|
|
||||||
one. This SHA also appears on those other two branches.
|
|
||||||
|
|
||||||
This command could be useful in a variety of situations.
|
|
||||||
|
|
||||||
1. If I'm looking at a commit on a branch and I cannot remember if it has been
|
|
||||||
integrated upstream yet. This check could tell me (unless commit squashing
|
|
||||||
happens).
|
|
||||||
2. If I'm on `main`, as I was above, and I am trying to remember what branch
|
|
||||||
introduced a commit, this can help with that sleuthing. This assumes I don't
|
|
||||||
delete branches.
|
|
||||||
3. Maybe I've just run a `git bisect` to track down a bad commit and I want to
|
|
||||||
see what are all the local branches that are impacted.
|
|
||||||
|
|
||||||
See `man git-branch` for more details.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Cherry Pick Multiple Commits At Once
|
|
||||||
|
|
||||||
I've always thought of `git cherry-pick` as being a command that you can run
|
|
||||||
against a single commit by specifying the SHA of that commit. That's how I've
|
|
||||||
always used it.
|
|
||||||
|
|
||||||
The man page for `git-cherry-pick` plainly states:
|
|
||||||
|
|
||||||
> Given one or more existing commits, apply the change each one introduces,
|
|
||||||
> recording a new commit for each.
|
|
||||||
|
|
||||||
We can cherry pick multiple commits at once in a single command. They will be
|
|
||||||
applied one at a time in the order listed.
|
|
||||||
|
|
||||||
Here we can see an example of applying two commits to the current branch and
|
|
||||||
the accompanying output as they are auto-merged.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git cherry-pick 5206af5 6362f41
|
|
||||||
Auto-merging test/services/event_test.rb
|
|
||||||
[jb/my-feature-branch 961f3deb] Use the other testing syntax
|
|
||||||
Date: Fri May 2 10:50:14 2025 -0500
|
|
||||||
1 file changed, 7 insertions(+), 7 deletions(-)
|
|
||||||
Auto-merging test/services/event_test.rb
|
|
||||||
[jb/my-feature-branch b15835d0] Make other changes to the test
|
|
||||||
Date: Fri May 2 10:54:48 2025 -0500
|
|
||||||
1 file changed, 7 insertions(+), 7 deletions(-)
|
|
||||||
```
|
|
||||||
|
|
||||||
If the commits cannot be cleanly merged, then you may need to do some manual
|
|
||||||
resolution as they are applied. Or maybe you want to try including the
|
|
||||||
`-Xpatience` merge strategy.
|
|
||||||
|
|
||||||
See `man git-cherry-pick` for more details. Make sure to look at the _Examples_
|
|
||||||
section which contains much more advanced examples beyond what is shown above.
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Clear Entries From Git Stash
|
|
||||||
|
|
||||||
I often stash changes as I'm moving between branches, rebasing, or pulling in
|
|
||||||
changes from the remote. Usually these are changes that I will want to restore
|
|
||||||
with a `git stash pop` in a few moments.
|
|
||||||
|
|
||||||
However, sometimes these stashed changes are abandoned to time.
|
|
||||||
|
|
||||||
When I run `git stash list` on an active project, I see that there are nine
|
|
||||||
entries in the list. When I do `git show stash@{0}` and `git show stash@{1}` to
|
|
||||||
see the changes that comprise the latest two entries, I don't see anything I
|
|
||||||
care about.
|
|
||||||
|
|
||||||
I can get rid of those individual entries with, say, `git stash drop
|
|
||||||
stash@{0}`.
|
|
||||||
|
|
||||||
But I'm pretty confident that I don't care about any of the nine entries in my
|
|
||||||
stash list, so I want to _clear_ out all of them. I can do that with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git stash clear
|
|
||||||
```
|
|
||||||
|
|
||||||
Now when I run `git stash list`, I see nothing.
|
|
||||||
|
|
||||||
See `man git-stash` for more details.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Determine Absolute Path Of Top-Level Project Directory
|
|
||||||
|
|
||||||
The `git rev-parse` command is a git plumbing command for parsing different
|
|
||||||
kinds of things in git into a canonical form that can be used in a deterministic
|
|
||||||
way by scripts. I would typically think of using it to work with branch names,
|
|
||||||
tags, and other kinds of refs.
|
|
||||||
|
|
||||||
There is a handy, sorta off-label use for it in determining the absolute path of
|
|
||||||
the root directory for the current git repository. Use the `--show-toplevel`
|
|
||||||
flag with no other arguments.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git rev-parse --show-toplevel
|
|
||||||
/Users/lastword/dev/jbranchaud/til
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, I am in the local copy of [my TIL repo](https://github.com/jbranchaud/til). This command gives me the absolute
|
|
||||||
path of the top-level directory where that `.git` directory resides.
|
|
||||||
|
|
||||||
This is useful for scripts that need to orient themselves to the current
|
|
||||||
project's top-level directory regardless of what directory they are being
|
|
||||||
executed from. This is useful for things like a git hook script or monorepos
|
|
||||||
with scripts located in a specific sub-project directory.
|
|
||||||
|
|
||||||
Also worth mentioning is the `--show-superproject-working-tree` flag. In my TIL
|
|
||||||
repo, I have a private repository included as a submodule. Within that directory
|
|
||||||
`--show-toplevel` will produce the absolute path to the submodule. If I instead
|
|
||||||
want the absolute path of the _super project_ (in this case TIL), then I can use
|
|
||||||
this other flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git rev-parse --show-toplevel
|
|
||||||
/Users/lastword/dev/jbranchaud/til/notes
|
|
||||||
|
|
||||||
❯ git rev-parse --show-superproject-working-tree
|
|
||||||
/Users/lastword/dev/jbranchaud/til
|
|
||||||
```
|
|
||||||
|
|
||||||
See `man git-rev-parse` for more details.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Display All Git Log Entries In My Local Timezone
|
|
||||||
|
|
||||||
I tend to work with remote teams distributed across across multiple time zones.
|
|
||||||
In that context, it is important to have an awareness of what time zone each
|
|
||||||
person is operating in and to communicate clearly around that.
|
|
||||||
|
|
||||||
When looking at the output for `git log` on a distributed team, the timestamps
|
|
||||||
for each entry can be all over the place. If I want to understand when something
|
|
||||||
was committed, I have to look at the time as well as the time zone offset and
|
|
||||||
mentally translate it to my own time zone.
|
|
||||||
|
|
||||||
There is a `git config` option to alleviate this issue by having `git log`
|
|
||||||
convert and display all timestamps into your local time zone.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git config --global log.date rfc-local
|
|
||||||
```
|
|
||||||
|
|
||||||
Running that will add this entry to your _global_ git config file:
|
|
||||||
|
|
||||||
```
|
|
||||||
[log]
|
|
||||||
date = rfc-local
|
|
||||||
```
|
|
||||||
|
|
||||||
Now the time that was displaying as `Wed Apr 8 20:12:33 2026 -0400` will display
|
|
||||||
as `Wed, 8 Apr 2026 19:12:33 -0500`.
|
|
||||||
|
|
||||||
This also helps with smoothing out differences from DST and for commits produced
|
|
||||||
by AI agents in sandbox environments where the locale is set to UTC.
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Exclude A Directory During A Command
|
|
||||||
|
|
||||||
Many of the git commands we use, such as `git add`, `git restore`, etc., target
|
|
||||||
files and paths relative to the current directory. This is typically exactly
|
|
||||||
what we want, to stage and unstage and so forth the files and directories in
|
|
||||||
front of us.
|
|
||||||
|
|
||||||
I recently ran into a situation where I needed to restore a small subset of
|
|
||||||
changes. At the same time, I had a massive number of auto-generated files
|
|
||||||
recording HTTP interactions (hundreds of files, modified on the working tree).
|
|
||||||
I wanted to run a `git restore`, but wading through all those HTTP recording
|
|
||||||
files was not feasible.
|
|
||||||
|
|
||||||
I needed to exclude those files. They all belonged to a `spec/cassettes`
|
|
||||||
directory. I could exclude them with a _pathspec_ magic signature pattern which
|
|
||||||
is used to alter and limit the paths in a git command.
|
|
||||||
|
|
||||||
A _pathspec_ magic signature is a special pattern made up of a `:` followed by
|
|
||||||
some signature declaring what the pattern means.
|
|
||||||
|
|
||||||
The `(exclude)`, `!`, and `^` magic signatures all mean the same thing —
|
|
||||||
exclude. So, we can exclude a directory from a `git restore` command like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git restore --patch -- . ':!spec/cassettes'
|
|
||||||
```
|
|
||||||
|
|
||||||
We've employed two pathspec patterns here. The first, `.`, scopes everything to
|
|
||||||
the current directory. The second, `':!spec/cassettes'` excludes everything in
|
|
||||||
the `spec/cassettes` directory.
|
|
||||||
|
|
||||||
See `man gitglossary` for more on _pathspecs_.
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Files With Local Changes Cannot Be Removed
|
|
||||||
|
|
||||||
This is a nice quality-of-life feature in `git` that should help you avoid
|
|
||||||
accidentally discarding changes that won't be retrievable.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git rm .tool-versions
|
|
||||||
error: the following file has local modifications:
|
|
||||||
.tool-versions
|
|
||||||
(use --cached to keep the file, or -f to force removal)
|
|
||||||
```
|
|
||||||
|
|
||||||
My `.tool-versions` file has some local changes. I don't realize that and I go
|
|
||||||
to issue a `git rm` command on that file. Instead of quietly wiping out my
|
|
||||||
changes, `git` lets me know I'm doing something destructive (these local
|
|
||||||
changes won't be in the diff or the reflog).
|
|
||||||
|
|
||||||
I can force the removal if I know what I'm doing with the `-f` flag. Or I can
|
|
||||||
take the two step approach of calling `git restore` on that file and then `git
|
|
||||||
rm`.
|
|
||||||
|
|
||||||
The `--cached` flag is also interesting because it doesn't actually delete the
|
|
||||||
file from my file system, but it does stage the file deletion with `git`. That
|
|
||||||
means the file now shows up as one of my untracked files.
|
|
||||||
|
|
||||||
See `man git-rm` for more details.
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Highlight Small Change On Single Line
|
|
||||||
|
|
||||||
Sometimes a change gets made on a single, long line of text in a Git tracked
|
|
||||||
file. If it is a small, subtle change, then it can be hard to pick out when
|
|
||||||
looking at the diff. A standard diff will show a green line of text stacked on
|
|
||||||
a red line of text with no more granular information.
|
|
||||||
|
|
||||||
There are two ways we can improve the diff output in these situations.
|
|
||||||
|
|
||||||
The first is built-in to git. It is the `--word-diff` flag which will visually
|
|
||||||
isolate the portions of the line that have changed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git diff --word-diff README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Which will produce something like this:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
A collection of concise write-ups on small things I learn [-day to day-]{+day-to-day+} across a
|
|
||||||
```
|
|
||||||
|
|
||||||
The outgoing part is wrapped in `[-...-]` and the incoming part is wrapped in
|
|
||||||
`{+...+}`.
|
|
||||||
|
|
||||||
The second (and my preference) is to use
|
|
||||||
[`delta`](https://github.com/dandavison/delta) as an external differ and pager
|
|
||||||
for git.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git -c core.pager=delta diff README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
I cannot visually demonstrate the difference in a standard code block. So I'll
|
|
||||||
describe it. We see a red and green line stacked on each other, but with muted
|
|
||||||
background colors. Then the specific characters that are different stand out
|
|
||||||
because they are highlighted with brighter red and green. I [posted a visual
|
|
||||||
here](https://bsky.app/profile/jbranchaud.bsky.social/post/3ln245orlxs2j).
|
|
||||||
|
|
||||||
`delta` can also be added as a standard part of your config like I demonstrate
|
|
||||||
in [Better Diffs With Delta](git/better-diffs-with-delta.md).
|
|
||||||
|
|
||||||
h/t to [Dillon Hafer's post on
|
|
||||||
`--word-diff`](https://til.hashrocket.com/posts/t994rwt3fg-finds-diffs-in-long-line)
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# List All Authors On Git Repository
|
|
||||||
|
|
||||||
The `git log` is the ledger of all commits made to the repository. If I am on
|
|
||||||
the `main` branch and I have the latest pulled from the remote, then running
|
|
||||||
`git log` will be a complete listing of all commits.
|
|
||||||
|
|
||||||
`git log` includes more information than just authorship. I can narrow that all
|
|
||||||
down to only author name and author email using the `--format` flag. For all the
|
|
||||||
format string options available, I can run `man git-log` and jump to the `PRETTY
|
|
||||||
FORMATS` section, scrolling just past the built-in formats.
|
|
||||||
|
|
||||||
The two I am interested in are `%an` (Author Name) and `%ae` (Author Email). I
|
|
||||||
can arrange these however I want in the format string argument. Here is what it
|
|
||||||
looks like for the [`egghead-next`
|
|
||||||
project](https://github.com/skillrecordings/egghead-next):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git log --format='%an <%ae>'
|
|
||||||
Zac Jones <zacjones93@gmail.com>
|
|
||||||
Zac Jones <zacjones93@gmail.com>
|
|
||||||
Zac Jones <zacjones93@gmail.com>
|
|
||||||
Zac Jones <zacjones93@gmail.com>
|
|
||||||
Zac Jones <zacjones93@gmail.com>
|
|
||||||
Zac Jones <zacjones93@gmail.com>
|
|
||||||
Zac Jones <zacjones93@gmail.com>
|
|
||||||
John Lindquist <johnlindquist@gmail.com>
|
|
||||||
Zac Jones <zacjones93@gmail.com>
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
I get _name_ followed by _email_ wrapped in angle brackets. This is only so
|
|
||||||
useful though because I am going to see tons duplicate authors especially for a
|
|
||||||
project with hundreds and thousands of commits. I can narrow this down with [a
|
|
||||||
deduplication trick via
|
|
||||||
`awk`](unix/deduplicate-list-while-preserving-original-order.md):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git log --format='%an <%ae>' | awk '!seen[$0]++'
|
|
||||||
Zac Jones <zacjones93@gmail.com>
|
|
||||||
John Lindquist <johnlindquist@gmail.com>
|
|
||||||
Josh Branchaud <jbranchaud@gmail.com>
|
|
||||||
Vojta Holik <vojta@egghead.io>
|
|
||||||
Creeland A. Provinsal <cree@egghead.io>
|
|
||||||
joel <joelhooks@gmail.com>
|
|
||||||
Creeland <cree@provinsal.com>
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
That's already a big improvement. The only other change I want to make is
|
|
||||||
related to the default ordering of `git log`. It lists out commits in descending
|
|
||||||
order (most recent first). I want to see authors listed in the order that they
|
|
||||||
first committed to the project. Adding in the `--reverse` flag will solve for
|
|
||||||
that.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git log --reverse --format='%an <%ae>' | awk '!seen[$0]++'
|
|
||||||
Joel Hooks <joelhooks@gmail.com>
|
|
||||||
johnlindquist <johnlindquist@gmail.com>
|
|
||||||
John Lindquist <johnlindquist@gmail.com>
|
|
||||||
William Johnson <w.alexander.johnson@gmail.com>
|
|
||||||
depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
|
|
||||||
Evgeniy Nagalskiy <evgeniy.nagalskiy@gmail.com>
|
|
||||||
Taylor Bell <taylorbell@gmail.com>
|
|
||||||
Maggie Appleton <maggie.fm.appleton@gmail.com>
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
See `man git-log` for more details.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# List All Commits Where File Was Added Or Deleted
|
|
||||||
|
|
||||||
I noticed I wasn't able to find a file with a specific name anywhere in my
|
|
||||||
codebase. I expected it to be there, so I wondered when it had been added and
|
|
||||||
deleted from the codebase according to the git commit history.
|
|
||||||
|
|
||||||
This calls for running a `git log`, but with a couple flags. First, I'll include
|
|
||||||
`--name-status` so that each commit that is listed includes the file names with
|
|
||||||
the change status (i.e. `A` for added and `D` for deleted). Then the
|
|
||||||
`--diff-filter` flag tells git that I am only looking for `A`dded and `D`eleted
|
|
||||||
files. Then the `--` indicates that a file path will follow, even a regex
|
|
||||||
pattern is valid here.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --name-status --diff-filter=AD -- '*_mock_payment_form.*'
|
|
||||||
```
|
|
||||||
|
|
||||||
I'm looking for what is called a _partial_ in Rails. I know the filename
|
|
||||||
consists of `_mock_payment_form`. The leading `*` is so that I can be vague
|
|
||||||
about the directory this might be found in. That's useful if this file was
|
|
||||||
potentially moved around. The trailing `.*` indicates the file extension which I
|
|
||||||
also want to be vague about.
|
|
||||||
|
|
||||||
For any of the commits returned by this `git log`, I can grab the SHA and run
|
|
||||||
`git show <SHA>` to see the full picture of that commit.
|
|
||||||
|
|
||||||
Another flag that might be useful to add is the `--all` flag which will look
|
|
||||||
across all refs/branches instead of just the branch (`main`) that I'm currently
|
|
||||||
on.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --all --name-status --diff-filter=AD -- '*_mock_payment_form.*'
|
|
||||||
```
|
|
||||||
|
|
||||||
Perhaps I'm thinking of a file that was added on an abandoned feature branch.
|
|
||||||
`--all` will help turn up that in the results as well.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# List All Git Aliases From gitconfig
|
|
||||||
|
|
||||||
Running the `git config --list` command will show all of the configuration
|
|
||||||
settings you have for `git` relative to your current location. Though most of
|
|
||||||
these setting probably live in `~/.gitconfig`, you may also have some locally
|
|
||||||
specified ones in `.git/config`. This will grab them all including any `alias`
|
|
||||||
entries.
|
|
||||||
|
|
||||||
We can narrow things down to just `alias` entries using the `--get-regexp` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git config --get-regexp '^alias\.'
|
|
||||||
|
|
||||||
alias.ap add --patch
|
|
||||||
alias.authors shortlog -s -n -e
|
|
||||||
alias.co checkout
|
|
||||||
alias.st status
|
|
||||||
alias.put push origin HEAD
|
|
||||||
alias.fixup commit --fixup
|
|
||||||
alias.squash commit --squash
|
|
||||||
alias.doff reset HEAD^
|
|
||||||
alias.add-untracked !git status --porcelain | awk '/\?\?/{ print $2 }' | xargs git add
|
|
||||||
alias.reset-authors commit --amend --reset-author -CHEAD
|
|
||||||
```
|
|
||||||
|
|
||||||
I use `git doff` all the time on feature branches to "pop" the latest commmit
|
|
||||||
onto the working copy. I was trying to remember exactly what the `git doff`
|
|
||||||
command is and this was an easy way to check.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# List And Count All Posts In TIL Repo
|
|
||||||
|
|
||||||
I want to be able to reliably list and count all posts in [my TIL
|
|
||||||
repo](https://github.com/jbranchaud/til). I do this to check that the count in
|
|
||||||
the README is accurate and in [the workflow
|
|
||||||
script](https://github.com/jbranchaud/jbranchaud/blob/71cba39dffb2bff68bf16d8895e435065e400250/scripts/update_tils.py#L101)
|
|
||||||
that powers [my GitHub Profile
|
|
||||||
README](https://github.com/jbranchaud/jbranchaud). In the past, I've used
|
|
||||||
pattern matching on the listing of all TILs in the
|
|
||||||
[README.md](https://github.com/jbranchaud/til/blob/master/README.md). That is
|
|
||||||
error prone and has required me to use two different markdown list styles.
|
|
||||||
|
|
||||||
A better approach is to ask `git` how many posts it currently has under version
|
|
||||||
control. I use a consistent directory structure where each TIL post is a
|
|
||||||
markdown file that is nested within a single category directory.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git ls-files -- */*.md
|
|
||||||
ack/ack-bar.md
|
|
||||||
ack/case-insensitive-search.md
|
|
||||||
ack/list-available-file-types.md
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Using `git ls-files` has the added benefit of only listing files that are
|
|
||||||
currently checked in to the project. So if I run this locally, it won't pick up
|
|
||||||
a draft post that hasn't been committed yet.
|
|
||||||
|
|
||||||
I can then pipe this to `wc -l` (count of all lines) to produce the count:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git ls-files -- */*.md | wc -l | xargs
|
|
||||||
1850
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: the empty `xargs` is a trick to trim the whitespace padding that `wc`
|
|
||||||
introduces.
|
|
||||||
|
|
||||||
See `man git-ls-files` for more details.
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# Mark A Release With An Annotated Tag
|
|
||||||
|
|
||||||
There are two kinds of tags in Git -- lightweight tags and annotated tags.
|
|
||||||
|
|
||||||
The [`git-tag` docs](https://git-scm.com/docs/git-tag) explain the distinction:
|
|
||||||
|
|
||||||
> Annotated tags are meant for release while lightweight tags are meant for
|
|
||||||
> private or temporary object labels.
|
|
||||||
|
|
||||||
When an annotated tag is created, a _tag object_ is created which has a creation
|
|
||||||
timestamp, a "tagger" (who created it), a message, and potentially a signature
|
|
||||||
if GPG commit signing is configured.
|
|
||||||
|
|
||||||
I can create an annotated tag for the `HEAD` commit like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git tag -a v0.1.0 -m "Release v0.1.0"
|
|
||||||
```
|
|
||||||
|
|
||||||
I can then inspect what was created in a number of ways using `git tag --list`,
|
|
||||||
`git show`, `git cat-file`, and `git log --show-signature`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git tag --list
|
|
||||||
v0.1.0
|
|
||||||
|
|
||||||
❯ git show --no-patch v0.1.0
|
|
||||||
tag v0.1.0
|
|
||||||
Tagger: jbranchaud <jbranchaud@gmail.com>
|
|
||||||
Date: Sun, 2 Aug 2026 13:57:37 -0500
|
|
||||||
|
|
||||||
Release v0.1.0
|
|
||||||
|
|
||||||
commit 8a533ecfda526ebd1a4695639830f5620dd8572d (HEAD -> main, tag: v0.1.0, origin/main, origin/HEAD)
|
|
||||||
Author: jbranchaud <jbranchaud@gmail.com>
|
|
||||||
Date: Sun, 2 Aug 2026 12:51:41 -0500
|
|
||||||
|
|
||||||
Add changelog with v0.1.0 release changes documented
|
|
||||||
|
|
||||||
❯ git cat-file -t v0.1.0
|
|
||||||
tag
|
|
||||||
|
|
||||||
❯ git cat-file -p v0.1.0
|
|
||||||
object 8a533ecfda526ebd1a4695639830f5620dd8572d
|
|
||||||
type commit
|
|
||||||
tag v0.1.0
|
|
||||||
tagger jbranchaud <jbranchaud@gmail.com> 1785697057 -0500
|
|
||||||
|
|
||||||
Release v0.1.0
|
|
||||||
|
|
||||||
❯ git log --show-signature
|
|
||||||
commit 8a533ecfda526ebd1a4695639830f5620dd8572d (HEAD -> main, tag: v0.1.0, origin/main, origin/HEAD)
|
|
||||||
gpg: Signature made Sun Aug 2 13:57:09 2026 CDT
|
|
||||||
gpg: using RSA key B2570A9DA3E2A537781501B11A8656918A8D016B
|
|
||||||
gpg: Good signature from "jbranchaud <jbranchaud@gmail.com>" [ultimate]
|
|
||||||
Author: jbranchaud <jbranchaud@gmail.com>
|
|
||||||
Date: Sun, 2 Aug 2026 12:51:41 -0500
|
|
||||||
|
|
||||||
Add changelog with v0.1.0 release changes documented
|
|
||||||
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
This tag will be included in a push when I run either of the following:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git push origin main
|
|
||||||
❯ git push origin v0.1.0
|
|
||||||
```
|
|
||||||
|
|
||||||
This tag, which is now tied to a _release_, can be seen at [_Releases /
|
|
||||||
v0.1.0_](https://github.com/jbranchaud/py-vmt/releases/tag/v0.1.0) on GitHub.
|
|
||||||
|
|
||||||
See `man git-tag` for more details.
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Programmatically Grab SHA For Head Commit
|
|
||||||
|
|
||||||
When I use `gh browse path/to/some-file.txt`, it opens the browser to that file
|
|
||||||
in GitHub. However, it targets the default branch (`main`) by default which is
|
|
||||||
not very useful as a permalink because what that file looks like on `main` is
|
|
||||||
liable to change.
|
|
||||||
|
|
||||||
There is a `--commit` flag you can use to have it instead open to that file at a
|
|
||||||
specific commit SHA.
|
|
||||||
|
|
||||||
So what SHA do I pass as an argument to that flag?
|
|
||||||
|
|
||||||
Often what I would like to grab is a reference to the current version of the
|
|
||||||
file which is whatever it looks like for the `HEAD` commit. But `HEAD` is
|
|
||||||
another moving target reference. The `git rev-parse` command can translate
|
|
||||||
`HEAD` into a specific SHA though.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git rev-parse --short HEAD
|
|
||||||
3402428
|
|
||||||
|
|
||||||
❯ git rev-parse HEAD
|
|
||||||
3402428aadc02cfdc9825c8feb593443e72f50cd
|
|
||||||
```
|
|
||||||
|
|
||||||
Either of those will work. I can use a bash command substitution then to tie it
|
|
||||||
all together into a single command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ gh browse path/to/some-file.txt --commit=$(git rev-parse --short HEAD)
|
|
||||||
```
|
|
||||||
|
|
||||||
See `man git-rev-parse` for more details.
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
While preparing some stats for a recent blog post on [A Decade of
|
While preparing some stats for a recent blog post on [A Decade of
|
||||||
TILs](https://www.visualmode.dev/a-decade-of-tils), I ran into an issue
|
TILs](https://www.visualmode.dev/a-decade-of-tils), I ran into an issue
|
||||||
referencing chunks of time further back than 2020.
|
referencing chuncks of time further back than 2020.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
❯ git diff --diff-filter=A --name-only HEAD@{2016-02-06}..HEAD@{2017-02-06} -- "*.md"
|
❯ git diff --diff-filter=A --name-only HEAD@{2016-02-06}..HEAD@{2017-02-06} -- "*.md"
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
# Restore File From One Branch To The Current
|
|
||||||
|
|
||||||
On one feature branch I have created some files and made changes to some
|
|
||||||
existing files as part of spiking a feature. Now I'm on a different branch
|
|
||||||
taking another shot at it. I want changes from one or two of the files. In the
|
|
||||||
past I've used `git-checkout` for this task. However, I believe this is one of
|
|
||||||
the use cases they had in mind when they added `git-restore`.
|
|
||||||
|
|
||||||
What I want to do is _restore_ the state of a file as it appears on some source
|
|
||||||
branch to my current branch. Here is what that looks like:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git restore --source=some-feature-branch app/models/contact.rb
|
|
||||||
```
|
|
||||||
|
|
||||||
Now when I check `git status` I'll see the state of that file on the
|
|
||||||
`some-feature-branch` branch overlayed on my current working copy. If the file
|
|
||||||
doesn't exist, it will be created.
|
|
||||||
|
|
||||||
See `man git-restore` for more details.
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
# Set Up GPG Signing Key
|
|
||||||
|
|
||||||
I wanted to have that "Verified" icon start showing up next to my commits in
|
|
||||||
GitHub. To do that, I need to generate a GPG key, configure the secret key in
|
|
||||||
GitHub, and then configure the public signing key with my git config.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# generate a gpg key
|
|
||||||
$ gpg --full-generate-key
|
|
||||||
|
|
||||||
# Pick the following options when prompted
|
|
||||||
# - Choose "RSA and RSA" (Options 1)
|
|
||||||
# - Max out key size at 4096
|
|
||||||
# - Choose expiration date (e.g. 0 for no expiration)
|
|
||||||
# - Enter "Real name" and "Email"
|
|
||||||
(I matched those to what is in my global git config)
|
|
||||||
# - Set passphrase (I had 1password generate a 4-word passphrase)
|
|
||||||
```
|
|
||||||
|
|
||||||
It may take a few seconds to create.
|
|
||||||
|
|
||||||
I can see it was created by listing my GPG keys.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gpg --list-secret-keys --keyid-format=long
|
|
||||||
[keyboxd]
|
|
||||||
---------
|
|
||||||
sec rsa4096/1A8656918A8D016B 2025-10-19 [SC]
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
I'll need the `1A8656918A8D016B` portion of that response for the next command
|
|
||||||
and it is what I set as my public signing key in my git config.
|
|
||||||
|
|
||||||
First, though, I add the full key block to my GitHub profile which I can copy
|
|
||||||
like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gpg --armor --export 1A8656918A8D016B | pbcopy
|
|
||||||
```
|
|
||||||
|
|
||||||
And then I paste that as a new GPG Key on GitHub under _Settings_ -> _SSH and
|
|
||||||
GPG Keys_.
|
|
||||||
|
|
||||||
Last, I update my global git config with the signing key and the preference to
|
|
||||||
sign commits:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git config --global user.signingkey 1A8656918A8D016B
|
|
||||||
git config --global commit.gpgsign true
|
|
||||||
```
|
|
||||||
|
|
||||||
Without `commit.gpgsign`, I would have to specify the `-S` flag every time I
|
|
||||||
want to create a signed commit.
|
|
||||||
|
|
||||||
[source](https://git-scm.com/book/ms/v2/Git-Tools-Signing-Your-Work)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Show Summary Stats For Current Branch
|
|
||||||
|
|
||||||
When I push a branch up to GitHub as a PR, there is a part of the UI that shows
|
|
||||||
you how many lines you've added and removed for this branch. It bases that off
|
|
||||||
the target branch which is typically your `main` branch.
|
|
||||||
|
|
||||||
The `git diff` command can provide those same stats right in the terminal. The
|
|
||||||
key is to specify the `--shortstat` flag which tells `git` to exclude other diff
|
|
||||||
output and only show:
|
|
||||||
|
|
||||||
- Number of files changed
|
|
||||||
- Number of insertions
|
|
||||||
- Number of deletions
|
|
||||||
|
|
||||||
Here is the summary stats for a branch I'm working on:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ git diff --shortstat main
|
|
||||||
8 files changed, 773 insertions(+), 25 deletions(-)
|
|
||||||
```
|
|
||||||
|
|
||||||
We have to be on our feature branch and then we point to the branch (or whatever
|
|
||||||
ref) we want to diff against. Since I want to know how my feature branch
|
|
||||||
compares to `main`, I specify that.
|
|
||||||
|
|
||||||
See `man git-diff` for more details.
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Skip Git Hooks As Needed
|
|
||||||
|
|
||||||
Projects have Git hooks configured for all sorts of reasons. Most common are
|
|
||||||
`pre-commit` hooks which verify certain aspects of the contents of a commit.
|
|
||||||
A `pre-commit` hook could check that the tests all pass, that the changes don't
|
|
||||||
include any debugging statements, and so forth. There are all kinds of hooks
|
|
||||||
though, like `pre-rebase` and `post-checkout`.
|
|
||||||
|
|
||||||
These hooks can sometimes get in the way and we may need to skip or disable them
|
|
||||||
on a one-off basis.
|
|
||||||
|
|
||||||
Several Git commands offer a `--no-verify` flag which can skip running the hook
|
|
||||||
associated with that command.
|
|
||||||
|
|
||||||
- `git commit --no-verify` (skips `pre-commit` and `commit-msg` hooks)
|
|
||||||
- `git push --no-verify` (skips `pre-push` hook)
|
|
||||||
- `git merge --no-verify` (skips `pre-merge-commit` hook)
|
|
||||||
- `git am --no-verify` (skips `applypatch-msg` and `pre-applypatch` hooks)
|
|
||||||
|
|
||||||
If you look in the `.git/hooks` directory, there are several other hooks not
|
|
||||||
covered by the above. So, what if I am doing an action like `git checkout` and I
|
|
||||||
want to skip the `post-checkout` hook?
|
|
||||||
|
|
||||||
I can override the `hooksPath` config for that one command with the `-c` flag.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ git -c core.hooksPath=/dev/null checkout ...
|
|
||||||
```
|
|
||||||
|
|
||||||
By setting it to `/dev/null`, it will find *no* hooks available, so none will be
|
|
||||||
executed for this command.
|
|
||||||
|
|
||||||
See `man git-config` for more details on `core.hooksPath`.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Undo Latest Changes Committed To Specific File
|
|
||||||
|
|
||||||
I'm reviewing the changes I've made in a PR before I request a review from my
|
|
||||||
team. There are a scattering of changes in one file that I've changed my mind
|
|
||||||
on. Everything else looks good though. So, I need to undo the changes in that
|
|
||||||
file before proceeding.
|
|
||||||
|
|
||||||
Manually undoing them is going to be clunky. There is a way to do it with `git
|
|
||||||
checkout`, but that is one of the ways in which `git-checkout` was overloaded
|
|
||||||
leading to the release of `git-restore`.
|
|
||||||
|
|
||||||
Let's use `git-restore` instead. By specifying a `--source`, I can tell `git`
|
|
||||||
what _ref_ in the commit history that file should be restored to. I'm on a
|
|
||||||
short-lived feature branch, so pointing to `main` is good enough.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git restore --source=main app/models/customer.rb
|
|
||||||
```
|
|
||||||
|
|
||||||
If I've changed a file at multiple points on this feature branch and I don't
|
|
||||||
want to undo all of them, then pointing to `main` is no longer going to work.
|
|
||||||
Instead, I can point to the commit right before the current one (`HEAD`) that
|
|
||||||
I'm trying to undo.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git restore --source=HEAD~ app/models/customer.rb
|
|
||||||
```
|
|
||||||
|
|
||||||
This really isn't much different than the `git-checkout` version, but I still
|
|
||||||
find it to be a little clearer.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git checkout HEAD~ -- app/models/customer.rb
|
|
||||||
```
|
|
||||||
|
|
||||||
See `man git-restore` for more details.
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Run Scheduled Action To Commit Regular Updates
|
|
||||||
|
|
||||||
The quintessential example of what GitHub Actions are used for is running CI
|
|
||||||
tasks like the test suite, type checker, linter, etc. Each CI step runs to
|
|
||||||
completion and either passes or fails and you see the results in the GitHub PR
|
|
||||||
interface. That's a great use case, but far from the only one. GitHub Actions
|
|
||||||
are a much more general-purpose execution environment that can be used for much
|
|
||||||
more.
|
|
||||||
|
|
||||||
Here is a recent example that expanded my mind a bit on what is possible with
|
|
||||||
GitHub actions. Imagine a GitHub Action that is scheduled to run once a day, it
|
|
||||||
executes a script that might make changes to the repo itself (e.g. the
|
|
||||||
`README.md`), and then commits those changes (self-updating the repo).
|
|
||||||
|
|
||||||
Here is a minimal version of a workflow that does that:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '17 11 * * *' # daily at 11:17 UTC (06:17 CT)
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4 # get the repo + push credentials
|
|
||||||
|
|
||||||
- run: ./bin/regenerate_readme.sh # apply updates to README
|
|
||||||
|
|
||||||
- run: |
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add -A README.md
|
|
||||||
git diff --staged --quiet || {
|
|
||||||
git commit -m "chore: regenerate README with latest updates"
|
|
||||||
git push
|
|
||||||
} # commit updates to README if there are any, otherwise no-op
|
|
||||||
```
|
|
||||||
|
|
||||||
Once a day at 11:17 UTC, GitHub Actions will run this workflow which is made up
|
|
||||||
of a single job with permissions to write to the repo.
|
|
||||||
|
|
||||||
- First, it checks out the repo in the job container which includes push
|
|
||||||
credentials.
|
|
||||||
- Then it runs a script that might update the README (e.g. maybe based on other
|
|
||||||
things that have since been committed to the repo).
|
|
||||||
- Last, it attempts to stage any changes the script made to the README. If there
|
|
||||||
are any, then it will commit them with that generic commit message. Finally it
|
|
||||||
will push that commit to the main branch.
|
|
||||||
|
|
||||||
If this all sounds a bit untethered from a real-world example, then take a look
|
|
||||||
at how I use this exact pattern to [apply daily updates to my GitHub Profile
|
|
||||||
README](https://github.com/jbranchaud/jbranchaud/blob/main/.github/workflows/update-tils.yml)
|
|
||||||
based on the latest [TILs](https://github.com/jbranchaud/til) I have written.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Access Your GitHub Profile Photo
|
|
||||||
|
|
||||||
Let's say I have my [GitHub profile](https://github.com/jbranchaud) pulled up in
|
|
||||||
the browser.
|
|
||||||
|
|
||||||
```
|
|
||||||
https://github.com/jbranchaud
|
|
||||||
```
|
|
||||||
|
|
||||||
If I then add `.png` to the end of that in the URL bar:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://github.com/jbranchaud.png
|
|
||||||
```
|
|
||||||
|
|
||||||
I'll be redirected to the URL where the full image file lives. In my case:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://avatars.githubusercontent.com/u/694063?v=4
|
|
||||||
```
|
|
||||||
|
|
||||||
You can pull up yours `https://github.com/<username>.png` to access your profile
|
|
||||||
image.
|
|
||||||
|
|
||||||
[source](https://dev.to/10xlearner/how-to-get-the-profile-picture-of-a-github-account-1d82)
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# Create And Push To New Repo From CLI
|
|
||||||
|
|
||||||
I figured there must be a good way to create a new repo in GitHub using the `gh`
|
|
||||||
CLI based on a local git repo. I spend so much time in existing git projects
|
|
||||||
that already have GitHub repos that I haven't had the chance to figure this out.
|
|
||||||
Until now.
|
|
||||||
|
|
||||||
I just finished a first pass on a fresh project for [a GitHub profile
|
|
||||||
README](https://github.com/jbranchaud/jbranchaud). It was time to put it up on
|
|
||||||
GitHub and see if worked. Instead of going through the GitHub web UI to create
|
|
||||||
this new repo, I found the `gh repo create` subcommand. I then asked Claude what
|
|
||||||
flags I needed for my use case. The recommendation was as follows:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ gh repo create --public --source=. --remote=origin --push
|
|
||||||
✓ Created repository jbranchaud/jbranchaud on github.com
|
|
||||||
https://github.com/jbranchaud/jbranchaud
|
|
||||||
✓ Added remote https://github.com/jbranchaud/jbranchaud.git
|
|
||||||
Enumerating objects: 11, done.
|
|
||||||
Counting objects: 100% (11/11), done.
|
|
||||||
Delta compression using up to 16 threads
|
|
||||||
Compressing objects: 100% (6/6), done.
|
|
||||||
Writing objects: 100% (11/11), 4.21 KiB | 4.21 MiB/s, done.
|
|
||||||
Total 11 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
|
|
||||||
To https://github.com/jbranchaud/jbranchaud.git
|
|
||||||
* [new branch] HEAD -> main
|
|
||||||
branch 'main' set up to track 'origin/main' by rebasing.
|
|
||||||
✓ Pushed commits to https://github.com/jbranchaud/jbranchaud.git
|
|
||||||
```
|
|
||||||
|
|
||||||
This created the repo on GitHub for my authenticated profile (`jbranchaud`)
|
|
||||||
using the name of the current directory (`jbranchaud`). It then setup the
|
|
||||||
`origin` remote to point to that repo on GitHub. It then pushed the current
|
|
||||||
state of `main` up to the remote.
|
|
||||||
|
|
||||||
- `--public` configures the created repo to be a public, rather than private,
|
|
||||||
one.
|
|
||||||
- `--source=.` tells the command to run for the current directory (I ran this
|
|
||||||
from the root of this new project)
|
|
||||||
- `--remote=origin` tells it what the remote should be called, though `origin`
|
|
||||||
is the default, so this wasn't strictly necessary
|
|
||||||
- `--push` tells the command to push to the remote once it is created and
|
|
||||||
configured
|
|
||||||
|
|
||||||
See `gh repo create --help` for more details and examples.
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# List PRs Awaiting Your Review
|
|
||||||
|
|
||||||
If you work on a software team or steward an open-source project, then there are
|
|
||||||
likely some open PRs that you've been tagged to review. I am usually able to
|
|
||||||
catch most review requests as they come up either from the GitHub email
|
|
||||||
notifications or by keeping an eye on the PRs tab of active projects. Sometimes
|
|
||||||
I get consumed by a task and something slips through the cracks.
|
|
||||||
|
|
||||||
There are a couple other ways to quickly check if anything is waiting on my
|
|
||||||
review.
|
|
||||||
|
|
||||||
From the web UI I can visit the following URL which will show all PRs across all
|
|
||||||
projects where my review has been requested:
|
|
||||||
|
|
||||||
[https://github.com/pulls/review-requested](https://github.com/pulls/review-requested)
|
|
||||||
|
|
||||||
The GitHub CLI (`gh`) can do the same and I can do it right from the terminal
|
|
||||||
instead of navigating several clicks within GitHub's web UI.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gh search prs --review-requested=@me --state=open
|
|
||||||
```
|
|
||||||
|
|
||||||
That too will list PRs across all projects that are open and awaiting my review.
|
|
||||||
|
|
||||||
If that one ends up being a little too noisy, you can also use `gh` to _list_
|
|
||||||
just PRs for the current project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gh pr list --search "review-requested:@me"
|
|
||||||
```
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Open A PR To An Unforked Repo
|
|
||||||
|
|
||||||
Sometimes I will clone a repo to explore the source code or to look into a
|
|
||||||
potential bug. If my curiosity takes me far enough to make some changes, then I
|
|
||||||
jump through the hoops of creating a fork, reconfiguring branches, pushing to my
|
|
||||||
fork, and then opening the branch as a PR against the original repo.
|
|
||||||
|
|
||||||
The `gh` CLI allows me to avoid all that hoop-jumping. Directly from the cloned
|
|
||||||
repo I can use `gh` to create a new PR. It will prompt me to creat a fork. If I
|
|
||||||
accept, it will seamlessly create it and then open a PR from my fork to the
|
|
||||||
original.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gh pr create
|
|
||||||
```
|
|
||||||
|
|
||||||
This allows me to create the PR with a few prompts from the CLI. If you prefer,
|
|
||||||
you can include the `--web` flag to open the PR creation screen directly in the
|
|
||||||
browser.
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# Open File To Specific Line In Browser
|
|
||||||
|
|
||||||
Often one of the best ways to point a teammate to a line of code is to share a
|
|
||||||
GitHub link to a specific file and line number. Sometimes even a specific
|
|
||||||
commit.
|
|
||||||
|
|
||||||
For the longest time I would manually open GitHub, navigate to that file, and so
|
|
||||||
forth. The `gh` CLI supports this with the `browse` subcommand and it takes way
|
|
||||||
less time if you already have the repo in your local filesystem.
|
|
||||||
|
|
||||||
For instance, if I want to point you to line 11 of the `zshrc.local` file in my
|
|
||||||
`dotfiles` repo, I can run the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gh browse zshrc.local:11
|
|
||||||
```
|
|
||||||
|
|
||||||
That would open a browser tab to
|
|
||||||
[https://github.com/jbranchaud/dotfiles/blob/main/zshrc.local?plain=1#L11](https://github.com/jbranchaud/dotfiles/blob/main/zshrc.local?plain=1#L11).
|
|
||||||
|
|
||||||
If I wanted a range of lines, I could change it from `11` to, say, `11-27`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gh browse zshrc.local:11-27
|
|
||||||
```
|
|
||||||
|
|
||||||
And I would see this in the browser --
|
|
||||||
[https://github.com/jbranchaud/dotfiles/blob/main/zshrc.local?plain=1#L11-L27](https://github.com/jbranchaud/dotfiles/blob/main/zshrc.local?plain=1#L11-L27).
|
|
||||||
|
|
||||||
Both of these URLs are pointing to the `main` branch. If I instead want to
|
|
||||||
reference a specific commit, I can use the `--commit` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gh browse zshrc.local:11-27 --commit=f2f9e78d4fc784643f725c88f7a5a7a077e7f261
|
|
||||||
```
|
|
||||||
|
|
||||||
I grabbed that from the latest commit in `git log`. That opens to
|
|
||||||
[https://github.com/jbranchaud/dotfiles/blob/f2f9e78d4fc784643f725c88f7a5a7a077e7f261/zshrc.local?plain=1#L11-L27](https://github.com/jbranchaud/dotfiles/blob/f2f9e78d4fc784643f725c88f7a5a7a077e7f261/zshrc.local?plain=1#L11-L27).
|
|
||||||
|
|
||||||
Another way of doing that would be to use `git rev-parse HEAD`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gh browse zshrc.local:11-27 --commit=$(git rev-parse HEAD)
|
|
||||||
```
|
|
||||||
|
|
||||||
See `gh browse --help` for more details.
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Process JSON Output From gh With jq
|
|
||||||
|
|
||||||
The `gh` (GitHub) CLI is useful for accessing data about your profile and
|
|
||||||
projects from the terminal. With the `--json` flag, we can access the data in a
|
|
||||||
structured way which is useful for scripting.
|
|
||||||
|
|
||||||
Here is an example of pulling a list of all my repositories, limiting each
|
|
||||||
entity to just the `nameWithOwner` and `description`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ gh repo list --limit 1000 --json nameWithOwner,description
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"description": "My personal site -- joshbranchaud.com",
|
|
||||||
"nameWithOwner": "jbranchaud/personal-site"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Private repo for the NOTES.md of my TIL repo",
|
|
||||||
"nameWithOwner": "jbranchaud/til-notes-private"
|
|
||||||
},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
If I'm using the `--json` flag, then I can add in the `--jq` flag to apply a
|
|
||||||
`jq` query for additional processing of the output.
|
|
||||||
|
|
||||||
Here I convert it to a series of tuples:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ gh repo list --limit 1000 --json nameWithOwner,description \
|
|
||||||
--jq '.[] | [.nameWithOwner, .description]'
|
|
||||||
[
|
|
||||||
"jbranchaud/personal-site",
|
|
||||||
"My personal site -- joshbranchaud.com"
|
|
||||||
]
|
|
||||||
[
|
|
||||||
"jbranchaud/til-notes-private",
|
|
||||||
"Private repo for the NOTES.md of my TIL repo"
|
|
||||||
]
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Then I can add one more pipe to that `jq` query to turn it into _tab-separated
|
|
||||||
values_ using
|
|
||||||
[`@tsv`](https://jqlang.org/manual/v1.5/#format-strings-and-escaping):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ gh repo list --limit 1000 --json nameWithOwner,description \
|
|
||||||
--jq '.[] | [.nameWithOwner, .description] | @tsv'
|
|
||||||
jbranchaud/personal-site My personal site -- joshbranchaud.com
|
|
||||||
jbranchaud/til-notes-private Private repo for the NOTES.md of my TIL repo
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful because I can then pipe it to another program, such as an `fzf`
|
|
||||||
command like [this repo selector that opens the selected one in the
|
|
||||||
browser](https://github.com/jbranchaud/dotfiles/commit/f964ca10c6c4db3475411c2991dc2f1dfd18c818).
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Target Another Repo When Creating A PR
|
|
||||||
|
|
||||||
I have a [`dotfiles` repo](https://github.com/jbranchaud/dotfiles) that I forked
|
|
||||||
from [`dkarter/dotfiles`](https://github.com/dkarter/dotfiles). I'm adding a
|
|
||||||
bunch of my own customizations on a `main` branch while continually pulling in
|
|
||||||
and merging upstream changes.
|
|
||||||
|
|
||||||
The primary remote according to `gh` is `jbranchaud/dotfiles`. 98% of the time
|
|
||||||
that is what I want. However, I occasionally want to share some changes upstream
|
|
||||||
via a PR. Running `gh pr create` as is will create a PR against my fork. To
|
|
||||||
override this on a one-off basis, I can use the `--repo` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gh pr create --repo dkarter/dotfiles
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a PR against `dkarter:master` from my branch (e.g.
|
|
||||||
[`jbranchaud:jb/fix-hardcoded-paths`](https://github.com/dkarter/dotfiles/pull/373)).
|
|
||||||
|
|
||||||
See `man gh-pr-create` for more details.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Tell gh What The Default Repo Is
|
|
||||||
|
|
||||||
I recently forked [dkarter/dotfiles](https://github.com/dkarter/dotfiles) as a
|
|
||||||
way of bootstrapping a robust dotfile config for a new machine that I could
|
|
||||||
start making customizations to. I'm maintaining a `my-dotfiles` branch and keep
|
|
||||||
things in sync with the original upstream repo.
|
|
||||||
|
|
||||||
When trying to go to *my* fork of the repo
|
|
||||||
([jbranchaud/dotfiles](https://github.com/jbranchaud/dotfiles)) in the web with
|
|
||||||
the `gh` CLI tool, I ran into a weird issue. It was instead opening up to
|
|
||||||
`dkarter/dotfiles`.
|
|
||||||
|
|
||||||
`gh` was under the wrong impression which repo should be considered the default.
|
|
||||||
To clarify things for `gh`, there is a command to set the default repo.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gh repo set-default jbranchaud/dotfiles
|
|
||||||
✓ Set jbranchaud/dotfiles as the default repository for the current directory
|
|
||||||
```
|
|
||||||
|
|
||||||
Now when I run `gh repo view --web`, it opens the browser to my fork of the
|
|
||||||
dotfiles.
|
|
||||||
|
|
||||||
But where does this setting live?
|
|
||||||
|
|
||||||
Opening this repo's `.git/config` file I can see a section for the `origin`
|
|
||||||
remote that includes a new line for `gh-resolved`. This being set to `base`
|
|
||||||
tells `gh` that this remote is the one to treat as the default repo.
|
|
||||||
|
|
||||||
```
|
|
||||||
[remote "origin"]
|
|
||||||
url = git@github.com:jbranchaud/dotfiles.git
|
|
||||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
||||||
gh-resolved = base
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
See `gh repo set-default --help` for more details.
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Check Ruby Version For Production App
|
|
||||||
|
|
||||||
While deploying a fresh Rails app to Heroku recently, I ran into an issue. The
|
|
||||||
`it` block argument wasn't working despite being on Ruby 4.0. Or so I thought.
|
|
||||||
|
|
||||||
Running the following command reported the Ruby version of that Heroku server
|
|
||||||
instance:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ heroku run -- ruby --version
|
|
||||||
Running ruby --version on ⬢ my-app... up, run.3090
|
|
||||||
ruby 3.3.9 (2025-07-24 revision f5c772fc7c) [x86_64-linux]
|
|
||||||
```
|
|
||||||
|
|
||||||
I was on `3.3.9` which must have been the fallback default at the time.
|
|
||||||
|
|
||||||
Though I had set the Ruby version in my `.ruby-version` file, I had neglected to
|
|
||||||
specify it in the `Gemfile` as well. Once I added it to the `Gemfile` and
|
|
||||||
redeployed, my Heroku server instance was running the expected version of Ruby.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ heroku run -- ruby --version
|
|
||||||
Running ruby --version on ⬢ my-app... up, run.5353
|
|
||||||
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [x86_64-linux]
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: because [I have set `HEROKU_ORGANIZATION` and
|
|
||||||
`HEROKU_APP`](set-default-team-and-app-for-project.md) in my environment
|
|
||||||
(`.envrc`) for the local copy of the app, I don't need to specify those when
|
|
||||||
running the `heroku run` command above.
|
|
||||||
|
|
||||||
See `heroku run --help` for more details.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Specify Default Team And App For Project
|
|
||||||
|
|
||||||
Typically when you run commands with the Heroku CLI you'll need to specify the
|
|
||||||
name of the app on Heroku you're targeting with the `--app` flag. However, to
|
|
||||||
first see the names of the apps you may want to run `heroku apps` (or `heroku
|
|
||||||
list`). That will list the apps for your default team.
|
|
||||||
|
|
||||||
If you need to see apps for a different team (i.e. organization), you'll need to
|
|
||||||
specify that team either with the `--team` flag or by setting that as an
|
|
||||||
environment variable.
|
|
||||||
|
|
||||||
Here I do the latter in an `.envrc` file:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Heroku
|
|
||||||
export HEROKU_ORGANIZATION=visualmode
|
|
||||||
```
|
|
||||||
|
|
||||||
Once that is set and the environment reloaded, running `heroku apps` will show
|
|
||||||
the apps specific to that team on Heroku.
|
|
||||||
|
|
||||||
Similarly, if you want to set a default app for your project so that you don't
|
|
||||||
have to always specify the `--app` flag, you can update your `.envrc`
|
|
||||||
accordingly.
|
|
||||||
|
|
||||||
```
|
|
||||||
# Heroku
|
|
||||||
export HEROKU_ORGANIZATION=visualmode
|
|
||||||
export HEROKU_APP=my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
I had a hard time finding official documentation for this which is why I'm
|
|
||||||
writing this up here. I've manually verified this works with my own team and
|
|
||||||
app.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Allow Number Input To Accept Decimal Values
|
|
||||||
|
|
||||||
Here is a number input element:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<input type="number" id="amount" required class="border" />
|
|
||||||
```
|
|
||||||
|
|
||||||
This renders an empty number input box with up and down arrows which will, by
|
|
||||||
default, increment or decrement the value by **1**.
|
|
||||||
|
|
||||||
Of course, I can manually edit the input typing in a value like `1.25`.
|
|
||||||
|
|
||||||
However, when I submit that via an HTML form, the submission will be prevented
|
|
||||||
and the browser will display a validation error.
|
|
||||||
|
|
||||||
> Please enter a valid value. The two nearest valid values are 1 and 2.
|
|
||||||
|
|
||||||
If I want to be able to input a decimal value like this, I need to change the
|
|
||||||
`step` value. It defaults to `1`, but I could change it to `2`, `10`, or in
|
|
||||||
this case to `0.01`.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<input type="number" step="0.01" id="amount" required class="border" />
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice now that as you click the up and down arrows, the value is incremented
|
|
||||||
and decremented by **0.01** at a time.
|
|
||||||
|
|
||||||
If I want to maintain the step value of `1` while allowing decimal values, I
|
|
||||||
can instead set the `step` value to be `any`.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<input type="number" step="any" id="amount" required class="border" />
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [MDN docs on number
|
|
||||||
inputs](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number)
|
|
||||||
for more details.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Download A Google Doc As Specific Format
|
|
||||||
|
|
||||||
I was recently given a public Google Doc URL and I was curious if I could
|
|
||||||
download it from the command line. I didn't want to have to install special CLI
|
|
||||||
though. I was hoping to use something like `curl`.
|
|
||||||
|
|
||||||
A brief chat with Claude and I learned that not only can I use `curl`, but I
|
|
||||||
can specify the format in the _export_ URL.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ export GOOGLE_DOC_URL="https://docs.google.com/document/d/157rMgHeBf76T9TZnUjtrUyyS2XPwG0tObr-OjYNfMaI"
|
|
||||||
|
|
||||||
$ echo $GOOGLE_DOC_URL
|
|
||||||
https://docs.google.com/document/d/157rMgHeBf76T9TZnUjtrUyyS2XPwG0tObr-OjYNfMaI
|
|
||||||
|
|
||||||
$ curl -L "$GOOGLE_DOC_URL/export?format=pdf" -o doc.pdf
|
|
||||||
% Total % Received % Xferd Average Speed Time Time Time Current
|
|
||||||
Dload Upload Total Spent Left Speed
|
|
||||||
100 414 0 414 0 0 2763 0 --:--:-- --:--:-- --:--:-- 2895
|
|
||||||
100 16588 0 16588 0 0 56214 0 --:--:-- --:--:-- --:--:-- 167k
|
|
||||||
|
|
||||||
$ ls doc.pdf
|
|
||||||
doc.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
I append `/export` and then include the `?format=pdf` query param to specify
|
|
||||||
that I want the document to be exported in PDF format. With the `-o` flag I can
|
|
||||||
specify the name and extension of the output file.
|
|
||||||
|
|
||||||
This is a handy on its own, but noticing that Google Docs supports other export
|
|
||||||
formats, I thought it would be useful to go back-and-forth with Claude to
|
|
||||||
sketch out a script that can do this and prompt me (with `fzf`) for the file
|
|
||||||
type -- [here is the gist for
|
|
||||||
`gdoc-download`](https://gist.github.com/jbranchaud/cf3d2028107a1bd8484eed7cca0fcdab).
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Grab The RSS Feed For A Substack Blog
|
|
||||||
|
|
||||||
I've been attempting to put more energy into finding and reading blog posts via
|
|
||||||
an RSS feed reader. This as opposed to scrolling and scrolling and hoping that
|
|
||||||
the algorithm turns up an interesting article or two.
|
|
||||||
|
|
||||||
A lot of people who have been blogging for a while have a handy RSS feed link
|
|
||||||
prominently displayed on their site. We love to see it!
|
|
||||||
|
|
||||||
There are a few people whose writing I really enjoy that distribute their words
|
|
||||||
via Substack. I couldn't find a prominent or not prominent RSS feed link
|
|
||||||
anywhere on someone's Substack. What I did learn, after some searching, is that
|
|
||||||
you can tack `/feed` onto the end of someone's Substack URL and that will give
|
|
||||||
you the XML feed.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```
|
|
||||||
Substack blog landing page URL:
|
|
||||||
https://registerspill.thorstenball.com
|
|
||||||
|
|
||||||
Substack blog RSS feed URL:
|
|
||||||
https://registerspill.thorstenball.com/feed
|
|
||||||
```
|
|
||||||
|
|
||||||
Grab that feed URL and paste it into your feed reader and you should start
|
|
||||||
seeing their stuff show up.
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Hide Overflowing Text For Google Sheets Column
|
|
||||||
|
|
||||||
I imported a big CSV into a new Google Sheets document. This included a
|
|
||||||
"Description" column with many of the descriptions varying between 50 and 80
|
|
||||||
characters. The bottom line is that the description column was flowing over the
|
|
||||||
top of the columns next to it. Instead of expanding the width of that column as
|
|
||||||
far as the largest description, I wanted to hide the _overflow_.
|
|
||||||
|
|
||||||
The way to do this in Google Sheets is to highlight the entire column by
|
|
||||||
clicking on the column grouping. Then under the _Format_ menu item is a
|
|
||||||
_Wrapping_ submenu. The _Clip_ option is what I was looking for because it clips
|
|
||||||
the text that gets shown at the edge of the column.
|
|
||||||
@@ -5,6 +5,8 @@ an array-like object with all of the arguments to the function. Even if not
|
|||||||
all of the arguments are referenced in the function signature, they can
|
all of the arguments are referenced in the function signature, they can
|
||||||
still be accessed via the `arguments` object.
|
still be accessed via the `arguments` object.
|
||||||
|
|
||||||
|
> For ES6+ compatibility, the `spread` operator used via [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters) is preferred over the `arugments` object when accessing an abritrary number of function arguments.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function argTest(one) {
|
function argTest(one) {
|
||||||
console.log(one);
|
console.log(one);
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
# Filter By Truthy Values With Boolean Function
|
|
||||||
|
|
||||||
The `Boolean` function (not to be confused with the `Boolean` constructor)
|
|
||||||
evaluates any given value to its [boolean
|
|
||||||
coercion](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean#boolean_coercion).
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
> Boolean(0)
|
|
||||||
false
|
|
||||||
> Boolean(1)
|
|
||||||
true
|
|
||||||
> Boolean(null)
|
|
||||||
false
|
|
||||||
> Boolean([])
|
|
||||||
true
|
|
||||||
```
|
|
||||||
|
|
||||||
One way that this can be put to use is as a _boolean identity function_ for
|
|
||||||
passing to other functions like `filter`.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
> [0, 1, "", [], "four", null, "six", undefined, 7].filter(Boolean)
|
|
||||||
[ 1, [], 'four', 'six', 7 ]
|
|
||||||
```
|
|
||||||
|
|
||||||
This filters out all the non-truthy values from a list.
|
|
||||||
|
|
||||||
Let's say I'm building a list of nav items that will be rendered to the UI for a
|
|
||||||
specific user. Based on permissions or feature flags, certain nav items may not
|
|
||||||
be available. Those "empty" entries can be filtered out in this way.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
nav_items = [
|
|
||||||
{ label: "Home", href: "/" },
|
|
||||||
isSystemAdmin && { label: "System", "/system" },
|
|
||||||
featureEnabled(user, "api") && { label: "API", "/api" },
|
|
||||||
].filter(Boolean)
|
|
||||||
```
|
|
||||||
|
|
||||||
If any of those conditional nav items evaluate to `false`, then they will be
|
|
||||||
filtered out. The resulting `nav_items` array is a clean list of actual nav
|
|
||||||
items I want to render.
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Format A List Of Items By Locale
|
|
||||||
|
|
||||||
The `Intl` module includes a [`ListFormat`
|
|
||||||
object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat)
|
|
||||||
which can be used to format a list of items in a consistent way across locales.
|
|
||||||
|
|
||||||
I've reinvented the wheel of writing a helper function numerous times across
|
|
||||||
projects for formatting a list of items that accounts for formatting based on
|
|
||||||
how many items there are. This built-in function handles that with the added
|
|
||||||
benefit of working across locales.
|
|
||||||
|
|
||||||
Here are lists of three, two, and one items formatted in the `long` styles for
|
|
||||||
US english.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
> const formatter = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' });
|
|
||||||
undefined
|
|
||||||
|
|
||||||
> formatter.format(['Alice', 'Bob', 'Carla'])
|
|
||||||
'Alice, Bob, and Carla'
|
|
||||||
|
|
||||||
> formatter.format(['Coffee', 'Tea'])
|
|
||||||
'Coffee and Tea'
|
|
||||||
|
|
||||||
> formatter.format(['Taco'])
|
|
||||||
'Taco'
|
|
||||||
```
|
|
||||||
|
|
||||||
The difference between `long` and `short` style for a `conjunction` is _and_
|
|
||||||
versus _&_. In addition to the type`conjunction`, you could also use
|
|
||||||
`disjunction` which will do an _or_ instead of an _and_. I'm not sure what
|
|
||||||
you'd use the `unit` type for.
|
|
||||||
|
|
||||||
You could use another locale, such as French, as well:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
> const formatter = new Intl.ListFormat('fr', { style: 'long', type: 'conjunction' });
|
|
||||||
undefined
|
|
||||||
|
|
||||||
> formatter.format(['café', 'thé'])
|
|
||||||
'café et thé'
|
|
||||||
```
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Get User's Preferred Language From Browser
|
|
||||||
|
|
||||||
A great way to determine a user's preferred language if you aren't able to ask
|
|
||||||
them directly is to look at the language setting for their browser's UI.
|
|
||||||
|
|
||||||
We can get this from the instance of
|
|
||||||
[`Navigator`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) in the
|
|
||||||
user's JavaScript runtime within the browser.
|
|
||||||
|
|
||||||
My browser's UI is set to US English, which I can verify like so:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
> navigator.language
|
|
||||||
'en-US'
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful for all sorts of things like [formatting dates for
|
|
||||||
display](basic-date-formatting-without-a-library.md):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
> const now = new Date();
|
|
||||||
> Intl.DateTimeFormat(navigator.language).format(now)
|
|
||||||
'5/14/2026'
|
|
||||||
```
|
|
||||||
|
|
||||||
Or for [formatting other kinds of units for
|
|
||||||
display](formatting-values-with-units-for-display.md):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
> const milesFormat =
|
|
||||||
Intl.NumberFormat(navigator.language, { style: "unit", unit: "mile" });
|
|
||||||
> milesFormat.format(1500)
|
|
||||||
"1,500 mi"
|
|
||||||
```
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# `npm run` Has Some Typo Aliases
|
|
||||||
|
|
||||||
The developers of the `npm` CLI know that sometimes we are trying to run
|
|
||||||
commands in a hurry. It's easy to be trying to type `npm run` and instead type
|
|
||||||
`npm rum` or `npm urn`. No worries though, the command will still work.
|
|
||||||
|
|
||||||
If I run `npm help run`, I'll see a manpage that opens with the following:
|
|
||||||
|
|
||||||
```
|
|
||||||
NPM-RUN(1) NPM-RUN(1)
|
|
||||||
|
|
||||||
NAME
|
|
||||||
npm-run - Run arbitrary package scripts
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
npm run <command> [-- <args>]
|
|
||||||
|
|
||||||
aliases: run-script, rum, urn
|
|
||||||
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice it lists a few _aliases_ including `rum` and `urn`.
|
|
||||||
|
|
||||||
Here are two examples of me running my test suite with `rum` and then `urn`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ npm rum test:run
|
|
||||||
|
|
||||||
> test:run
|
|
||||||
> vitest run
|
|
||||||
|
|
||||||
|
|
||||||
RUN v3.0.7 /Users/lastword/dev/jbranchaud/still
|
|
||||||
|
|
||||||
✓ app/javascript/utils/urlUtils.test.js (6 tests) 2ms
|
|
||||||
✓ app/javascript/utils/clipboardImage.test.js (20 tests) 3ms
|
|
||||||
|
|
||||||
Test Files 2 passed (2)
|
|
||||||
Tests 26 passed (26)
|
|
||||||
Start at 22:07:53
|
|
||||||
Duration 298ms (transform 17ms, setup 0ms, collect 22ms, tests 5ms, environment 270ms, prepare 63ms)
|
|
||||||
|
|
||||||
|
|
||||||
❯ npm urn test:run
|
|
||||||
|
|
||||||
> test:run
|
|
||||||
> vitest run
|
|
||||||
|
|
||||||
|
|
||||||
RUN v3.0.7 /Users/lastword/dev/jbranchaud/still
|
|
||||||
|
|
||||||
✓ app/javascript/utils/urlUtils.test.js (6 tests) 3ms
|
|
||||||
✓ app/javascript/utils/clipboardImage.test.js (20 tests) 3ms
|
|
||||||
|
|
||||||
Test Files 2 passed (2)
|
|
||||||
Tests 26 passed (26)
|
|
||||||
Start at 22:07:58
|
|
||||||
Duration 305ms (transform 20ms, setup 0ms, collect 25ms, tests 6ms, environment 269ms, prepare 61ms)
|
|
||||||
```
|
|
||||||
|
|
||||||
See `npm help run` for more details.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Describe Current Changes And Create New Change
|
|
||||||
|
|
||||||
One of the first patterns I learned with `jj` was a pair of commands to
|
|
||||||
essentially "commit" the working copy and start a fresh, new change. So if I am
|
|
||||||
done making some changes, I can add a description to the `(no description)`
|
|
||||||
working copy and then start a new working copy _change_.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ jj describe -m "Add status subcommand to show current status"
|
|
||||||
$ jj new
|
|
||||||
```
|
|
||||||
|
|
||||||
I learned from [Steve](https://steveklabnik.com/) in the [jj
|
|
||||||
discord](https://discord.gg/dkmfj3aGQN) that a shorthand for this pattern is to
|
|
||||||
use the `jj commit` command directly.
|
|
||||||
|
|
||||||
> When called without path arguments or `--interactive`, `jj commit` is
|
|
||||||
> equivalent to `jj describe` followed by `jj new`.
|
|
||||||
|
|
||||||
That means, instead of the above pair of commands, I could have done:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ jj commit -m "Add status subcommand to show current status"
|
|
||||||
```
|
|
||||||
|
|
||||||
That would have had the same result in my case. However, notice the caveats
|
|
||||||
mentioned in the quote above and check out `man jj-commit` for more details on
|
|
||||||
that.
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Squash Changes Into Parent Commit Interactively
|
|
||||||
|
|
||||||
While I have some changes in progress as part of the working copy, I can squash
|
|
||||||
them into the previous / parent commit with the `jj squash` command. Running
|
|
||||||
that command as is will apply all the working copy changes to the parent leaving
|
|
||||||
the current revision empty.
|
|
||||||
|
|
||||||
I can also interactively squash those changes similar in spirit to how I might
|
|
||||||
use `git add --patch` to stage and then amend specific changes into the previous
|
|
||||||
commit with `git`. This can be done with [`jj`](https://github.com/jj-vcs/jj)
|
|
||||||
using `squash` with the `-i` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
jj squash -i # or --interactive
|
|
||||||
```
|
|
||||||
|
|
||||||
This will open up a TUI where I can click around or use keys. Each file in the
|
|
||||||
source revision (in my case, the working copy) will be listed. I can move the
|
|
||||||
cursor between them hitting _space_ to toggle them in or out of the squash
|
|
||||||
selection.
|
|
||||||
|
|
||||||
I can also hit `f` over a given file to toggle _folding_. When folding is on, a
|
|
||||||
diff of the file will be disclosed with checkboxes for toggling individual
|
|
||||||
hunks and lines.
|
|
||||||
|
|
||||||
Once I'm satisfied with my interactive selection, I can hit `c` to confirm and
|
|
||||||
only the selected files and changes will be squashed into the parent.
|
|
||||||
|
|
||||||
See `man jj-squash` for more details.
|
|
||||||
|
|
||||||
[source](https://steveklabnik.github.io/jujutsu-tutorial/real-world-workflows/the-squash-workflow.html)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Count Number Of Tokens In A File
|
|
||||||
|
|
||||||
Over time you have accumulated a bunch of small directives, corrections, and
|
|
||||||
project details in your `CLAUDE.md` or `AGENTS.md` file. The file doesn't seem
|
|
||||||
too big, but you are mindful that it is being included in every prompt. How many
|
|
||||||
tokens is it eating from the context window?
|
|
||||||
|
|
||||||
OpenAI's BPE (Byte Pair Encoding) tokenization library,
|
|
||||||
[`tiktoken`](https://github.com/openai/tiktoken), is an open-source Python
|
|
||||||
package. If it is installed on our machine, then we can use it as part of the
|
|
||||||
following one-liner to check a file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ python -c "import tiktoken, sys; print(len(tiktoken.encoding_for_model('gpt-4o').encode(open(sys.argv[1], 'r', encoding='utf-8').read())))" \
|
|
||||||
AGENTS.md
|
|
||||||
1018
|
|
||||||
```
|
|
||||||
|
|
||||||
I ran this against the `AGENTS.md` file in a team project I'm on. It came out to
|
|
||||||
1018 tokens. This is a very good approximation based on the tokenizer trained
|
|
||||||
for `gpt-4o`. The tokenizers may vary a little from model to model, but the
|
|
||||||
differences for our purposes here are going to be negligible.
|
|
||||||
|
|
||||||
This one-liner gets the "first" argument to the command, reads it in, and runs
|
|
||||||
that string against the tokenizer. The length of the tokenized encoding is then
|
|
||||||
printed.
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# Include A File With Message To `ant`
|
|
||||||
|
|
||||||
The [Anthropic CLI tool](https://github.com/anthropics/anthropic-cli) (`ant`)
|
|
||||||
allows including files, such as images, directly in the message being sent to
|
|
||||||
the model. This is done with the `@` symbol followed by a relative path to the
|
|
||||||
image on the file system.
|
|
||||||
|
|
||||||
I have a file `parameterized-behavioral-tests.png` in my current directory that
|
|
||||||
I'd like to include. In the `content` array of the message I include an object
|
|
||||||
of type `image` along with some `source` metadata. Within `source` the image
|
|
||||||
file is referenced with the `data` field.
|
|
||||||
|
|
||||||
With that set, I then include a `text` object with my prompt asking for alt text
|
|
||||||
of this image.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ ant messages create \
|
|
||||||
--model claude-opus-5 \
|
|
||||||
--max-tokens 1024 \
|
|
||||||
--message '{role: user, content: [
|
|
||||||
{type: image, source: {type: base64, media_type: image/png, data: "@./parameterized-behavioral-tests.png"}},
|
|
||||||
{type: text, text: "Produce a concise, but descriptive alt text for this image."}
|
|
||||||
]}' \
|
|
||||||
--transform 'content.#(type=="text").text' --raw-output
|
|
||||||
|
|
||||||
A hand-drawn style diagram comparing two testing approaches, split by a vertical
|
|
||||||
line.
|
|
||||||
|
|
||||||
On the left, under the heading "Behavioral Tests," is a single column of six
|
|
||||||
rounded rectangular test rows: four outlined in green with green check-mark
|
|
||||||
icons (passing) and two outlined in red with red X icons (failing), each
|
|
||||||
containing black scribble lines representing text.
|
|
||||||
|
|
||||||
On the right, under the heading "Parameterized Behavioral Tests," a bracketed
|
|
||||||
list of three purple parameter symbols — a circle, a triangle, and a diamond —
|
|
||||||
sits at the top, with arrows pointing down to three separate columns of six test
|
|
||||||
rows each. Every row in a column is tagged with its corresponding parameter
|
|
||||||
shape on the right edge. The circle column shows all six rows passing (green
|
|
||||||
with check marks). The triangle column shows three passing and three failing
|
|
||||||
(red with X marks). The diamond column shows five passing and one failing. The
|
|
||||||
illustration conveys that a single behavioral test, when parameterized, expands
|
|
||||||
into multiple variants whose pass/fail outcomes can differ per parameter.
|
|
||||||
```
|
|
||||||
|
|
||||||
This is for [the image toward the top of this post on parameterized tests in
|
|
||||||
pytest](https://www.visualmode.dev/parameterize-a-fixture-instead-of-a-test-case-with-pytest).
|
|
||||||
The description is a bit wordier than I would have liked, but it is spot on. It
|
|
||||||
is both visually descriptive and conveys what is being conceptually illustrated.
|
|
||||||
|
|
||||||
This whole thing was 3800 input tokens, 328 output tokens, and cost ~$0.02.
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Access CoreUtils That Conflict With Unix Utilities
|
|
||||||
|
|
||||||
In [another post about GNU CoreUtils](add-a-bunch-of-cli-utilities-with-coreutils.md) I explained how to
|
|
||||||
install and use some of the provided utilities. The utilities I referenced were
|
|
||||||
`seq` and `realpath` which are novel on MacOS. There are other CoreUtils that
|
|
||||||
would conflict with existing system utilities. To avoid the conflicts, [those
|
|
||||||
utilities are installed with `g`
|
|
||||||
prefix](https://unix.stackexchange.com/a/729136).
|
|
||||||
|
|
||||||
A good example of this is `mv` and `gmv`. These are both utilities for moving
|
|
||||||
files and largely behave the same. They are a few subtle differences though.
|
|
||||||
|
|
||||||
Here is the manpage for the built-in `mv` utility:
|
|
||||||
|
|
||||||
```
|
|
||||||
NAME
|
|
||||||
mv – move files
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
mv [-f | -i | -n] [-hv] source target
|
|
||||||
mv [-f | -i | -n] [-v] source ... directory
|
|
||||||
```
|
|
||||||
|
|
||||||
And here is the manpage for `gmv`:
|
|
||||||
|
|
||||||
```
|
|
||||||
NAME
|
|
||||||
mv - move (rename) files
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
mv [OPTION]... [-T] SOURCE DEST
|
|
||||||
mv [OPTION]... SOURCE... DIRECTORY
|
|
||||||
mv [OPTION]... -t DIRECTORY SOURCE...
|
|
||||||
```
|
|
||||||
|
|
||||||
There are some different forms and flags available. `gmv` for instance supports
|
|
||||||
a `-t` flag which specifies the _target_ directory so that all other listed
|
|
||||||
paths are treated as sources of the move. This makes `gmv` easier to use with
|
|
||||||
`xargs`, for instance.
|
|
||||||
|
|
||||||
Look through `fd '^g' /opt/homebrew/bin/` and you'll notice a bunch of them like
|
|
||||||
`gls`, `gln`, `ghead`, `gwhoami`, `gyes`, etc. You can also look through all of
|
|
||||||
them in the [`coreutils.json` homebrew formula](https://formulae.brew.sh/api/formula/coreutils.json).
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Add A Bunch Of CLI Utilities With coreutils
|
|
||||||
|
|
||||||
The [`coreutils`](https://www.gnu.org/software/coreutils/) project is a
|
|
||||||
collection of useful utilities that every operating system ought to have.
|
|
||||||
|
|
||||||
> The GNU Core Utilities are the basic file, shell and text manipulation
|
|
||||||
> utilities of the GNU operating system. These are the core utilities which are
|
|
||||||
> expected to exist on every operating system.
|
|
||||||
|
|
||||||
While many of these utilities are redundant with BSD utilities that MacOS
|
|
||||||
chooses to ship with, there are some differences in the overlapping ons and then
|
|
||||||
many additions from `coreutils`.
|
|
||||||
|
|
||||||
They can be installed with Homebrew:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ brew install coreutils
|
|
||||||
```
|
|
||||||
|
|
||||||
And then you should have some new things available on your path. Take `shuf`, for
|
|
||||||
instance. This utility can shuffle and select items from a file or incoming
|
|
||||||
lines from another command. Here I use it to randomly grab a number between 1
|
|
||||||
and 5 (with the help of `seq`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ seq 1 5 | shuf -n 1
|
|
||||||
3
|
|
||||||
|
|
||||||
❯ seq 1 5 | shuf -n 1
|
|
||||||
2
|
|
||||||
|
|
||||||
❯ seq 1 5 | shuf -n 1
|
|
||||||
5
|
|
||||||
```
|
|
||||||
|
|
||||||
Or how about some utilities for manipulating file names? Among others there is
|
|
||||||
`realpath`, `basename`, and `dirname`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ realpath README.md
|
|
||||||
/Users/lastword/dev/jbranchaud/til/README.md
|
|
||||||
|
|
||||||
❯ realpath README.md | xargs basename
|
|
||||||
README.md
|
|
||||||
|
|
||||||
❯ realpath README.md | xargs dirname
|
|
||||||
/Users/lastword/dev/jbranchaud/til
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [manual](https://www.gnu.org/software/coreutils/manual/coreutils.html)
|
|
||||||
for many more details.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Capture Screenshoot To Clipboard From CLI
|
|
||||||
|
|
||||||
MacOS comes with a `screencapture` utility that you can run from the terminal
|
|
||||||
to activate the built-in screenshot functionality on Mac.
|
|
||||||
|
|
||||||
Usually when I am taking a screenshot, I want to do something with it right
|
|
||||||
away. Such as paste it into an application or group chat. The `-c` flag forces
|
|
||||||
the screen capture to go the clipboard.
|
|
||||||
|
|
||||||
I also generally want to capture a specific area of the screen so that the
|
|
||||||
captured image includes the right amount of context and nothing more. The `-i`
|
|
||||||
flag puts you in interactive screen capture mode. That means your cursor will
|
|
||||||
turn into a crosshair that you can use to make a drag selection of the capture
|
|
||||||
area.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ screencapture -ic
|
|
||||||
```
|
|
||||||
|
|
||||||
Select an area to capture, it's now on your clipboard, paste it where you need
|
|
||||||
it.
|
|
||||||
|
|
||||||
Note: The first time you run this command, your terminal program (e.g. iTerm2)
|
|
||||||
may prompt you for the necessary OS permissions in order to capture images of
|
|
||||||
your screen. You'll need to grant those permissions and then rerun the command.
|
|
||||||
|
|
||||||
See `man screencapture` for more details.
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# Clean Up Item Layout In Finder Window
|
|
||||||
|
|
||||||
Sometimes while doing a bunch of manual drag-n-drop of files and folders in a
|
|
||||||
Finder.app window, I'll end up with a visual mess. Compared to other folders,
|
|
||||||
nothing is organized on the grid.
|
|
||||||
|
|
||||||
I can tell Finder.app to clean that up with the _Clean Up_ menu option.
|
|
||||||
|
|
||||||
While focused on the folder that I'm concerned about, I can go to _View_ >
|
|
||||||
_Clean Up_ in the top menu. Everything will snap into place.
|
|
||||||
|
|
||||||
On the specific Finder.app window, there is also a triple-dot actions menu that
|
|
||||||
appears on the top right. The _Clean Up_ action is available there as well.
|
|
||||||
|
|
||||||
There is also a _Clean Up By_ option which is a nice way to organize by some
|
|
||||||
attribute, such as the type (e.g Folder/File and extension).
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Control Which Monitor App Switcher Appears On
|
|
||||||
|
|
||||||
For the most part when I hit `cmd+tab` (and `cmd+shift+tab`) to switch between
|
|
||||||
apps, the visual switcher UI (which shows a row of the open apps) appears on my
|
|
||||||
main monitor. However, sometimes I will be hitting `cmd+tab` and nothing shows
|
|
||||||
up on my main monitor. I look to the right at my side monitor and there is the
|
|
||||||
app switcher UI.
|
|
||||||
|
|
||||||
Why is it appearing over there all of a sudden?
|
|
||||||
|
|
||||||
The reason is that the app switcher UI is anchored to the same screen where the
|
|
||||||
doc is located. Though the doc defaults to my main monitor, if I access the doc
|
|
||||||
from the side monitor, now it is anchored there.
|
|
||||||
|
|
||||||
To switch it back, I just have to make the doc slide up on my main monitor by
|
|
||||||
running my mouse down to the bottom of that screen.
|
|
||||||
|
|
||||||
The switch up was because I accidentally accessed the doc on my side monitor
|
|
||||||
without realizing.
|
|
||||||
|
|
||||||
[source](https://superuser.com/a/744680)
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Detect How Long A User Has Been Idle
|
|
||||||
|
|
||||||
The `ioreg` utility on MacOS dumps the I/O Kit registry tree. This lets us look
|
|
||||||
at the state of all hardware devices and drivers registered with I/O Kit.
|
|
||||||
Looking specifically at the Human Interface Device subsystem (`IOHIDSystem`), we
|
|
||||||
can find a handful of properties including the `HIDIdleTime`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ ioreg -c IOHIDSystem | awk '/HIDIdleTime/'
|
|
||||||
| | | "HIDIdleTime" = 91831000
|
|
||||||
```
|
|
||||||
|
|
||||||
That value is the number of nanoseconds since a human input device was last
|
|
||||||
interacted with. That is the amount of time the user (me) has been idle.
|
|
||||||
|
|
||||||
I can convert this to seconds, which is the small amount of time between me
|
|
||||||
hitting enter in the terminal and the command finding the idle time.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ ioreg -c IOHIDSystem | awk '/HIDIdleTime/ {printf "%.2f seconds\n", $NF/1000000000}'
|
|
||||||
0.13 seconds
|
|
||||||
```
|
|
||||||
|
|
||||||
I can run this in `watch` to see the elapsed idle time increment.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
watch -n 1 "echo -n 'Idle time: '; ioreg -c IOHIDSystem | awk '/HIDIdleTime/ {printf \"%.1f seconds\\n\", \$NF/1000000000}'"
|
|
||||||
```
|
|
||||||
|
|
||||||
After watching the _idle time_ increment for a bit, I can move the mouse and
|
|
||||||
watch it reset on the next `watch` loop.
|
|
||||||
|
|
||||||
This could be used as part of a script that takes certain actions after the user
|
|
||||||
has been idle for a while, like putting the display to sleep or stopping a time
|
|
||||||
tracker app.
|
|
||||||
|
|
||||||
There is a _lot_ going on in the `ioreg` output and it's hard to make sense of
|
|
||||||
hardly any of it. I found running `ioreg -c IOHIDSystem | less`, searching for
|
|
||||||
`IdleTime`, and browsing from there to be a good starting point.
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Inspect Assertions Preventing Sleep
|
|
||||||
|
|
||||||
The `pmset` command is for inspecting and manipulating _Power Management
|
|
||||||
Settings_ on MacOS. The `-g` flag is for _getting_ details. We can get a summary
|
|
||||||
of power assertions with `-g assertions`. These assertions are ways that the
|
|
||||||
system and display are prevented from sleeping.
|
|
||||||
|
|
||||||
A common assertion preventing sleep is the user being active. Another example of
|
|
||||||
an assertion is a program like `caffeinate` that sets a timeout preventing sleep
|
|
||||||
for a fixed period of time.
|
|
||||||
|
|
||||||
Here I activate a 30 minute (1600 second) `caffeinate` session and then I
|
|
||||||
inspect the power management assertions which shows the details of that
|
|
||||||
assertion as well as two others.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ caffeinate -t 1600 &
|
|
||||||
[1] 98217
|
|
||||||
|
|
||||||
❯ pmset -g assertions
|
|
||||||
2025-11-02 13:20:57 -0600
|
|
||||||
Assertion status system-wide:
|
|
||||||
BackgroundTask 0
|
|
||||||
ApplePushServiceTask 0
|
|
||||||
UserIsActive 1
|
|
||||||
PreventUserIdleDisplaySleep 0
|
|
||||||
PreventSystemSleep 0
|
|
||||||
ExternalMedia 0
|
|
||||||
PreventUserIdleSystemSleep 1
|
|
||||||
NetworkClientActive 0
|
|
||||||
Listed by owning process:
|
|
||||||
pid 98217(caffeinate): [0x00045477000194b3] 00:00:03 PreventUserIdleSystemSleep named: "caffeinate command-line tool"
|
|
||||||
Details: caffeinate asserting for 1600 secs
|
|
||||||
Localized=THE CAFFEINATE TOOL IS PREVENTING SLEEP.
|
|
||||||
Timeout will fire in 1597 secs Action=TimeoutActionRelease
|
|
||||||
pid 145(WindowServer): [0x00044f2f00099212] 00:00:00 UserIsActive named: "com.apple.iohideventsystem.queue.tickle serviceID:10009be9e service:AppleUserHIDEventService product:CTRL Keyboard eventType:3"
|
|
||||||
Timeout will fire in 600 secs Action=TimeoutActionRelease
|
|
||||||
pid 80(powerd): [0x00044f2f00019216] 00:22:34 PreventUserIdleSystemSleep named: "Powerd - Prevent sleep while display is on"
|
|
||||||
```
|
|
||||||
|
|
||||||
See `man pmset` and `man caffeinate` for more details.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Launch Some Confetti
|
|
||||||
|
|
||||||
If you have [Raycast](https://www.raycast.com/) installed on your machine, then
|
|
||||||
you have quick access to some confetti via their quick command palette. Trigger
|
|
||||||
the command palette to open, start typing `confetti` until it appears as the
|
|
||||||
focused option, and then hit enter.
|
|
||||||
|
|
||||||
🎉
|
|
||||||
|
|
||||||
We can launch confetti other ways, including programmatically from scripts.
|
|
||||||
|
|
||||||
To do this, we need to first find the _deeplink_ for the Raycast _confetti_
|
|
||||||
program. Trigger the command palette and type out `confetti` again. However,
|
|
||||||
this time instead of hitting enter, hit `Cmd+k` to open other actions. Find the
|
|
||||||
_Copy Deeplink_ option.
|
|
||||||
|
|
||||||
You should now have this on your clipboard:
|
|
||||||
|
|
||||||
```
|
|
||||||
raycast://extensions/raycast/raycast/confetti
|
|
||||||
```
|
|
||||||
|
|
||||||
With this deeplink in hand, we can now trigger confetti other places. The
|
|
||||||
easiest way to do this is to open a terminal and pass that deep link as an
|
|
||||||
argument to `open`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ open raycast://extensions/raycast/raycast/confetti
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you can wrap that up in any old bash script or even just tack it on to the
|
|
||||||
end of a run of your test suite:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ rails test && open raycast://extensions/raycast/raycast/confetti
|
|
||||||
```
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Prevent Sleep With The Caffeinate Command
|
|
||||||
|
|
||||||
MacOS has a built-in utility `caffeinate` that can programatically prevent your
|
|
||||||
machine from sleeping. There are two kinds of sleep that it can prevent via
|
|
||||||
_assertions_.
|
|
||||||
|
|
||||||
> caffeinate creates assertions to alter system sleep behavior.
|
|
||||||
|
|
||||||
The two kinds of sleep behavior are _display sleep_ and _system idle sleep_. An
|
|
||||||
assertion to prevent display sleep can be created with `-d` and system idle
|
|
||||||
sleep with `-i`.
|
|
||||||
|
|
||||||
We can combine those to prevent both and then specify a duration (_timeout_)
|
|
||||||
with `-t` (with a value in seconds).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
caffeinate -d -i -t 600
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates assertions with 10 minute timeouts for both display and system idle
|
|
||||||
sleep.
|
|
||||||
|
|
||||||
The `caffeinate` command is blocking, so if you want to start it in the
|
|
||||||
background, you can do that like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
caffeinate -d -i -t 600 &
|
|
||||||
```
|
|
||||||
|
|
||||||
See `man caffeinate` for more details.
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
# Read The Lid Angle Sensor For A MacBook
|
|
||||||
|
|
||||||
MacOS has a bunch of internal HID (Human Interface Device) data that can surface
|
|
||||||
details about all kinds of "devices" that comprise your machine. Some obvious
|
|
||||||
ones are the keyboard and trackpad as well as external mice and keyboards. The
|
|
||||||
battery and power source details are another which is sometimes integrated into
|
|
||||||
tools that display battery status (e.g.
|
|
||||||
[`tmux-battery`](https://github.com/tmux-plugins/tmux-battery)), though it uses
|
|
||||||
`pmset` directly). And many, many more.
|
|
||||||
|
|
||||||
One example I'd never considered is that there is a sensor for the lid angle of
|
|
||||||
the laptop that can tell the system whether the lid is open or closed and how
|
|
||||||
open it is (i.e. at what angle). There is no public interface for this lid angle
|
|
||||||
sensor, but people exploring all the HID devices have found the identifiers that
|
|
||||||
correspond to it (e.g.
|
|
||||||
[`pybooklid`](https://github.com/tcsenpai/pybooklid/blob/main/pybooklid/macbook_lid.py)).
|
|
||||||
|
|
||||||
Here is a minimal script that uses `uv`, `hidapi` (python bindings), and
|
|
||||||
`libhidapi` (shared runtime lib for those bindings):
|
|
||||||
|
|
||||||
```python
|
|
||||||
#!/usr/bin/env -S uv run --quiet --script
|
|
||||||
# /// script
|
|
||||||
# requires-python = ">=3.10"
|
|
||||||
# dependencies = ["hidapi"]
|
|
||||||
# ///
|
|
||||||
"""Print MacBook lid angle in degrees."""
|
|
||||||
import os, sys
|
|
||||||
|
|
||||||
if sys.platform == "darwin":
|
|
||||||
brew = "/opt/homebrew/lib"
|
|
||||||
if os.path.exists(brew):
|
|
||||||
os.environ["DYLD_LIBRARY_PATH"] = f"{brew}:{os.environ.get('DYLD_LIBRARY_PATH','')}"
|
|
||||||
|
|
||||||
import hid
|
|
||||||
|
|
||||||
VENDOR_ID, PRODUCT_ID = 0x05AC, 0x8104
|
|
||||||
USAGE_PAGE, USAGE = 0x0020, 0x008A
|
|
||||||
REPORT_ID = 1
|
|
||||||
|
|
||||||
def read_angle():
|
|
||||||
for info in hid.enumerate(VENDOR_ID, PRODUCT_ID):
|
|
||||||
if info.get("usage_page") == USAGE_PAGE and info.get("usage") == USAGE:
|
|
||||||
d = hid.device()
|
|
||||||
path = info["path"]
|
|
||||||
d.open_path(path if isinstance(path, bytes) else path.encode())
|
|
||||||
try:
|
|
||||||
data = d.get_feature_report(REPORT_ID, 8)
|
|
||||||
if data and len(data) >= 3:
|
|
||||||
return float((data[2] << 8) | data[1])
|
|
||||||
finally:
|
|
||||||
d.close()
|
|
||||||
return None
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
a = read_angle()
|
|
||||||
if a is None:
|
|
||||||
sys.exit("sensor not available")
|
|
||||||
print(f"{a:.0f}")
|
|
||||||
```
|
|
||||||
|
|
||||||
These IDs and usage values are the undocumented values that allow the script to
|
|
||||||
navigate specifically to the lid angle sensor and specifically to the usage page
|
|
||||||
and value that represent the current lid angle reading.
|
|
||||||
|
|
||||||
```
|
|
||||||
VENDOR_ID, PRODUCT_ID = 0x05AC, 0x8104
|
|
||||||
USAGE_PAGE, USAGE = 0x0020, 0x008A
|
|
||||||
REPORT_ID = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
I added [this
|
|
||||||
script](https://github.com/jbranchaud/dotfiles/blob/cbc7196607d1d6b25885f5387ca85b658bd765de/bin/lidangle)
|
|
||||||
to [my dotfiles](https://github.com/jbranchaud/dotfiles) and made it executable
|
|
||||||
(`chmod +x bin/lidangle`) so that I can try it out. I first ran it while it was
|
|
||||||
closed and connected to my external monitor (`0`), then I opened it as far as it
|
|
||||||
could go (`129`), and then I tried angling it close to what I thought was 90
|
|
||||||
degress (`92`, so close).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ lidangle
|
|
||||||
0
|
|
||||||
|
|
||||||
❯ lidangle
|
|
||||||
129
|
|
||||||
|
|
||||||
❯ lidangle
|
|
||||||
92
|
|
||||||
```
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Reveal Location Of File In Finder.app
|
|
||||||
|
|
||||||
In the terminal I have the path to an image file. I want to open Finder.app to
|
|
||||||
the location of that image file so that I can drag and drop it into a file
|
|
||||||
upload area in the browser.
|
|
||||||
|
|
||||||
Instead of opening a Finder.app window and navigating directory by directory to
|
|
||||||
the location, I can use the `open` command. Using `open` directly with the image
|
|
||||||
file will open the image in Preview.app. I want to reveal the directory that the
|
|
||||||
image file is in within Finder.app. _Reveal_ is the keyword and the `-R` flag
|
|
||||||
does just that.
|
|
||||||
|
|
||||||
Here is an example of this that I actually ran when uploading a screenshot that
|
|
||||||
went into [this blogmark post](https://still.visualmode.dev/blogmarks/255):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ open -R /Users/lastword/images/tiobe-index-graph-march-2026.png
|
|
||||||
```
|
|
||||||
|
|
||||||
See `man open` for more details.
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Set Default Search Directory For Finder
|
|
||||||
|
|
||||||
In MacOS's Finder.app, when I click the Search (magnifying glass) icon and type
|
|
||||||
something in, it performs the search across all of "This Mac". This has always
|
|
||||||
really bugged me because I'm usually already in the directory that I want to be
|
|
||||||
specifically searching in.
|
|
||||||
|
|
||||||
A small quality-of-life improvement for me was to update the setting that
|
|
||||||
controls this to search in the current directory instead.
|
|
||||||
|
|
||||||
This can be done via the Finder.app menu at the bottom of the _Advanced_ section
|
|
||||||
in the _Settings_. The three setting options are:
|
|
||||||
|
|
||||||
- Search This Mac
|
|
||||||
- Search the Current Folder
|
|
||||||
- Search the Previous Search Scope
|
|
||||||
|
|
||||||
This can also be controlled from the command line using the `defaults write`
|
|
||||||
command. The specific setting is called `FXDefaultSearchScope` and the above
|
|
||||||
three settings translate to:
|
|
||||||
|
|
||||||
- `SCev` (Search This Mac)
|
|
||||||
- `SCcf` (Search the Current Folder)
|
|
||||||
- `SCsp` (Search the Previous Search Scope)
|
|
||||||
|
|
||||||
I can see the current default setting with `defaults read`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ defaults read com.apple.finder FXDefaultSearchScope
|
|
||||||
SCev
|
|
||||||
```
|
|
||||||
|
|
||||||
I can then change it to the _Search the Current Folder_ option like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ defaults write com.apple.finder FXDefaultSearchScope -string "SCcf"
|
|
||||||
```
|
|
||||||
|
|
||||||
All the open Finder windows still hold the original setting. I need to do a
|
|
||||||
`killall Finder` to effectively reload them. Now if I try doing a search, it
|
|
||||||
should default to the current directory instead of _This Mac_.
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# Convert Arbitrary Number To Probability With Sigmoid
|
|
||||||
|
|
||||||
A sigmoid function is a useful function in statistics and machine learning for
|
|
||||||
converting a number in the range of positive and negative real numbers into a
|
|
||||||
value between 0 and 1. Sigmoid functions can be a bit more diverse than this,
|
|
||||||
but this is a good basic definition.
|
|
||||||
|
|
||||||
Wikipedia defines another characteristic of sigmoid functions:
|
|
||||||
|
|
||||||
> A sigmoid function is any mathematical function whose graph has a
|
|
||||||
> characteristic S-shaped or sigmoid curve.
|
|
||||||
|
|
||||||
This S-shape is because it is asymptotic at the ends allowing it to cover all
|
|
||||||
real numbers in either direction.
|
|
||||||
|
|
||||||
A common sigmoid function and the one used by [PyTorch's `Sigmoid`](https://docs.pytorch.org/docs/2.13/generated/torch.nn.Sigmoid.html)
|
|
||||||
is this exponential form -- `σ(x) = 1 / (1 + exp(-x))`.
|
|
||||||
|
|
||||||
Here is what this looks like plotted on a graph:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
This function can be used any time we want to convert an arbitrary number into a
|
|
||||||
probability. Large negative numbers will approach 0. Large positive numbers will
|
|
||||||
approach 1. Numbers near 0 will settle somewhere in the middle.
|
|
||||||
|
|
||||||
Here are a few examples run through PyTorch's `sigmoid` function:
|
|
||||||
|
|
||||||
```python
|
|
||||||
print("σ(-99) => ", torch.sigmoid(torch.tensor(-99.0)))
|
|
||||||
print("σ(99) => ", torch.sigmoid(torch.tensor(99.0)))
|
|
||||||
print("σ(0.123) => ", torch.sigmoid(torch.tensor(0.123)))
|
|
||||||
print("σ(-2) => ", torch.sigmoid(torch.tensor(-2.0)))
|
|
||||||
print("σ(1) => ", torch.sigmoid(torch.tensor(1.0)))
|
|
||||||
```
|
|
||||||
|
|
||||||
which prints out:
|
|
||||||
|
|
||||||
```
|
|
||||||
σ(-99) => tensor(0.)
|
|
||||||
σ(99) => tensor(1.)
|
|
||||||
σ(0.123) => tensor(0.5307)
|
|
||||||
σ(-2) => tensor(0.1192)
|
|
||||||
σ(1) => tensor(0.7311)
|
|
||||||
```
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Generate Permutations Of All Valid 9-ball Racks
|
|
||||||
|
|
||||||
I wanted to produce a full listing of all valid rack arrangements for the game
|
|
||||||
of [9-ball](https://en.wikipedia.org/wiki/Nine-ball). The constraints on how a
|
|
||||||
9-ball rack can be arranged are, first, that the 1 ball must be placed at the
|
|
||||||
head of the diamond and, second, that the 9 ball must be placed at the center of
|
|
||||||
the diamond. After that, all other balls (2 through 8) can be placed in any
|
|
||||||
arrangement.
|
|
||||||
|
|
||||||
Because each of those seven remaining balls can be arranged in distinct
|
|
||||||
orderings where each ball is placed once, this is a
|
|
||||||
[_permutation_](https://en.wikipedia.org/wiki/Permutation) problem.
|
|
||||||
|
|
||||||
> In elementary combinatorics, the k-permutations, or partial permutations, are
|
|
||||||
> the ordered arrangements of k distinct elements selected from a set. When k is
|
|
||||||
> equal to the size of the set, these are the permutations in the previous
|
|
||||||
> sense.
|
|
||||||
|
|
||||||
For this problem, the seven distinct elements can be arranged into `7!` (seven
|
|
||||||
factorial) unique permutations. That is, 5040 permutations.
|
|
||||||
|
|
||||||
I can use [Ruby's `Array#permutations`
|
|
||||||
method](https://docs.ruby-lang.org/en/4.0/Array.html#method-i-permutation) to
|
|
||||||
enumerate these 5040 permutations like so:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
[2,3,4,5,6,7,8].permutation.map do |perm|
|
|
||||||
[1, *perm[0..2], 9, *perm[3..7]]
|
|
||||||
end.to_a
|
|
||||||
=> [[1, 2, 3, 4, 9, 5, 6, 7, 8],
|
|
||||||
[1, 2, 3, 4, 9, 5, 6, 8, 7],
|
|
||||||
[1, 2, 3, 4, 9, 5, 7, 6, 8],
|
|
||||||
[1, 2, 3, 4, 9, 5, 7, 8, 6],
|
|
||||||
[1, 2, 3, 4, 9, 5, 8, 6, 7],
|
|
||||||
[1, 2, 3, 4, 9, 5, 8, 7, 6],
|
|
||||||
[1, 2, 3, 4, 9, 6, 5, 7, 8],
|
|
||||||
...
|
|
||||||
[1, 8, 7, 6, 9, 5, 3, 2, 4],
|
|
||||||
[1, 8, 7, 6, 9, 5, 3, 4, 2],
|
|
||||||
[1, 8, 7, 6, 9, 5, 4, 2, 3],
|
|
||||||
[1, 8, 7, 6, 9, 5, 4, 3, 2]]
|
|
||||||
```
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Create Umbrella Task For All Test Tasks
|
|
||||||
|
|
||||||
When I was first sketching out the [`mise` tasks](https://mise.jdx.dev/tasks/running-tasks.html) for a Rails app, I added
|
|
||||||
the following two tasks. One is for running all the `rspec` tests. The other is
|
|
||||||
for running all the `vitest` (JavaScript) tests.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[tasks."test:rspec"]
|
|
||||||
run = "unbuffer bundle exec rspec"
|
|
||||||
description = "Run RSpec tests"
|
|
||||||
depends = ["bundle-install"]
|
|
||||||
|
|
||||||
[tasks."test:vitest"]
|
|
||||||
run = "unbuffer yarn test run"
|
|
||||||
description = "Run Vitest tests"
|
|
||||||
depends = ["node-install"]
|
|
||||||
```
|
|
||||||
|
|
||||||
I didn't want to have to invoked both of this individually every time I wanted
|
|
||||||
to run the full suite. So I added a `test:all` task to do it all.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[tasks."test:all"]
|
|
||||||
description = "Run all tests (RSpec and Vitest)"
|
|
||||||
run = [
|
|
||||||
"unbuffer bundle exec rspec",
|
|
||||||
"unbuffer yarn test run",
|
|
||||||
]
|
|
||||||
description = "Run RSpec tests"
|
|
||||||
depends = ["bundle-install", "node-install"]
|
|
||||||
```
|
|
||||||
|
|
||||||
This worked (for now). But it ate at me, for a couple reasons. I had to
|
|
||||||
duplicate everything about the existing `test:rspec` and `test:vitest` tasks.
|
|
||||||
And this didn't account for a new kind of test task being added (e.g.
|
|
||||||
`test:e2e`).
|
|
||||||
|
|
||||||
Instead, I can rely on `depends` and wildcards to achieve this without the
|
|
||||||
duplication which makes it more future-proof.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[tasks."test:all"]
|
|
||||||
description = "Run all tests (RSpec and Vitest)"
|
|
||||||
depends = ["test:*"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Running `mise run test:all` won't execute its own command, but because it
|
|
||||||
depends on all other `test:*` tasks, the tests will get run through those
|
|
||||||
dependencies.
|
|
||||||
|
|
||||||
This task naming pattern also allows for calling all tests with `mise run "test:**"`.
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# List The Files Being Loaded By Mise
|
|
||||||
|
|
||||||
While running `mise` for the first time, after adding a `mise.toml` file to a
|
|
||||||
project, I noticed something strange. Instead of invoking the command I had
|
|
||||||
specified (`mise run dev`), several parellel tool downloads were kicked off. In
|
|
||||||
addition to Ruby, it was installing an older version of Postgres, and lua. What
|
|
||||||
gives?
|
|
||||||
|
|
||||||
By running `mise cfg`, I can list all the files being loaded by `mise` and get
|
|
||||||
to the bottom of this.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mise cfg
|
|
||||||
|
|
||||||
Path Tools
|
|
||||||
~/.tool-versions node, ruby, postgres, lua
|
|
||||||
~/code/still/.ruby-version ruby
|
|
||||||
~/code/still/Gemfile (none)
|
|
||||||
~/code/still/.tool-versions ruby
|
|
||||||
~/code/still/mise.toml (none)
|
|
||||||
```
|
|
||||||
|
|
||||||
I was only thinking about the files local to my project and I forgot that I
|
|
||||||
have a system-wide `.tool-versions` file. As we can see from the output, that
|
|
||||||
file specifies `postgres` and `lua` as well. Mise wanted to ensure that it had
|
|
||||||
downloaded the specified versions of each of those tools before running my
|
|
||||||
task.
|
|
||||||
|
|
||||||
[source](https://mise.jdx.dev/configuration.html)
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Look In Ruby Version Dotfile
|
|
||||||
|
|
||||||
Newer versions of [`mise`](https://mise.jdx.dev/dev-tools/) specifically only
|
|
||||||
look for tool versions in `mise.toml` as well as the asdf `.tool-versions` file.
|
|
||||||
A lot of Ruby projects use the `.ruby-version` file to indicate the Ruby version
|
|
||||||
of a project. To continue to use the `.ruby-version` file instead of migrating
|
|
||||||
to `mise.toml`, you need to tell `mise` that you prefer to use the idiomatic
|
|
||||||
version file.
|
|
||||||
|
|
||||||
I added the following line to my
|
|
||||||
[`~/.config/mise/config.toml`](https://github.com/jbranchaud/dotfiles/commit/8edeb7a9c53500e89e88b4079cbd1859ebebcbda)
|
|
||||||
file:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
idiomatic_version_file_enable_tools = ["ruby"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, whenever `mise` is looking for the specified Ruby version of a project, it
|
|
||||||
will also look for `.ruby-version`.
|
|
||||||
|
|
||||||
Here is a [full list of idomatic version files supported by
|
|
||||||
`mise`](https://mise.jdx.dev/configuration.html#idiomatic-version-files).
|
|
||||||
|
|
||||||
See
|
|
||||||
[`idiomatic_version_file_enable_tools`](https://mise.jdx.dev/configuration/settings.html#idiomatic_version_file_enable_tools)
|
|
||||||
as well as the [Ruby-specific documentation](https://mise.jdx.dev/lang/ruby.html#ruby-version-and-gemfile-support)
|
|
||||||
for more details.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Override Your Project Mise File
|
|
||||||
|
|
||||||
A project I'm working on has a version-controlled `.mise.toml` file in it. Some
|
|
||||||
changes were made to that recently that introduce some env vars that conflict
|
|
||||||
with my setup. If I make edits to that file, then I have a modified version of
|
|
||||||
`.mise.toml` sitting in my Git working copy.
|
|
||||||
|
|
||||||
```
|
|
||||||
# .mise.toml
|
|
||||||
[env]
|
|
||||||
CONFIG_SETTING = "project"
|
|
||||||
```
|
|
||||||
|
|
||||||
Instead, I can rely on the loading precedence rules of `mise` to override those
|
|
||||||
project settings with my individual settings. I can do that with the
|
|
||||||
`.mise.local.toml` file which is played on top of any `mise` configuration from
|
|
||||||
files further down the precedence chain.
|
|
||||||
|
|
||||||
```
|
|
||||||
# .mise.local.toml
|
|
||||||
[env]
|
|
||||||
CONFIG_SETTING = "override"
|
|
||||||
```
|
|
||||||
|
|
||||||
Assuming I have `mise` setup with my shell environment to automatically load in
|
|
||||||
these files, I can now check what takes precedence:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ echo $CONFIG_SETTING
|
|
||||||
override
|
|
||||||
```
|
|
||||||
|
|
||||||
Make sure `.mise.local.toml` is included in the `.gitignore` file to avoid
|
|
||||||
checking in your personal environment overrides.
|
|
||||||
|
|
||||||
To be sure about what files are loaded and in what order, give `mise cfg` a try.
|
|
||||||
I discuss that in more detail in [List The Files Being Loaded By Mise](list-the-files-being-loaded-by-mise.md).
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Pick From Tasks Using Interactive Picker
|
|
||||||
|
|
||||||
In [Add Mise Tasks For Common Workflow
|
|
||||||
Commands](https://www.visualmode.dev/add-mise-tasks-for-common-workflow-commands),
|
|
||||||
I wrote about a set of tasks I added as shortcuts for connecting to the `rails console` in various environments.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# mise.toml
|
|
||||||
[tasks."console:staging"]
|
|
||||||
description = "Open a Rails console on staging"
|
|
||||||
run = "ssh -t my-app-staging dokku run my-app rails console"
|
|
||||||
|
|
||||||
[tasks."console:prod"]
|
|
||||||
description = "Open a Rails console on production"
|
|
||||||
run = "ssh -t my-app-prod dokku run my-app rails console"
|
|
||||||
```
|
|
||||||
|
|
||||||
When a project is configured with multiple `mise` tasks like this, we can invoke
|
|
||||||
`mise run` without any specific arguments and it will prompt you with an
|
|
||||||
interactive picker. The picker will populate with all the tasks like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
❯ mise run
|
|
||||||
Tasks
|
|
||||||
Select a task to run
|
|
||||||
❯ console:prod Open a Rails console on production
|
|
||||||
console:staging Open a Rails console on staging
|
|
||||||
/
|
|
||||||
esc clear filter • enter confirm
|
|
||||||
```
|
|
||||||
|
|
||||||
We can navigate between the options with the arrow keys (and if we exit _filter_
|
|
||||||
mode by hitting `esc`, then `j/k` also work to move down and up). While in
|
|
||||||
_filter_ mode, we can type into the prompt which will filter the list of
|
|
||||||
commands down to just the partial matches.
|
|
||||||
|
|
||||||
Once we're targeting the task we want to run, we hit `enter` and the task is
|
|
||||||
executed.
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# Preserve Color Output For Task Command
|
|
||||||
|
|
||||||
I decided to wrap a couple test running commands for a project into a single
|
|
||||||
`test:all` mise task. It looked something like this:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[tasks."test:all"]
|
|
||||||
run = """
|
|
||||||
bundle exec rspec
|
|
||||||
yarn test run
|
|
||||||
"""
|
|
||||||
description = "Run all tests (RSpec and Vitest)"
|
|
||||||
depends = ["bundle-install", "node-install"]
|
|
||||||
```
|
|
||||||
|
|
||||||
I can run this with `mise run test:all` and it works. However, there is a
|
|
||||||
glaring issue that immediately juts out. All of the test runner output is
|
|
||||||
uncolored text. I'm used to and strongly prefer greens (passes), reds (fails),
|
|
||||||
and yellows (skips) of test runner output.
|
|
||||||
|
|
||||||
The test runners lose the text coloring when run through `mise` because they
|
|
||||||
believe they are not running in _interactive_ mode.
|
|
||||||
|
|
||||||
The [`expect`](https://linux.die.net/man/1/expect) tools (`brew install
|
|
||||||
expect`) install with another binary called
|
|
||||||
[`unbuffer`](https://linux.die.net/man/1/unbuffer). `unbuffer` can coerce a
|
|
||||||
command to run in interactive mode. Prepending these test runner commands with
|
|
||||||
`unbuffer` will preserve the colors as the results are output to the terminal.
|
|
||||||
|
|
||||||
Here is the update `test:all` task:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[tasks."test:all"]
|
|
||||||
run = """
|
|
||||||
unbuffer bundle exec rspec
|
|
||||||
unbuffer yarn test run
|
|
||||||
"""
|
|
||||||
description = "Run all tests (RSpec and Vitest)"
|
|
||||||
depends = ["bundle-install", "node-install"]
|
|
||||||
```
|
|
||||||
|
|
||||||
For some commands, it seems able to stream out (rather than _buffer_) the
|
|
||||||
results (e.g. with `vitest`). Whereas with `rspec`, the test suite runs to
|
|
||||||
completion and is then output to the terminal. I'm still investigating
|
|
||||||
streaming the `rspec` results.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Read Existing Dot Env File Into Env Vars
|
|
||||||
|
|
||||||
Just about any web app that I've worked on has had a `.env` file as a way of
|
|
||||||
configuring aspects of the app specific to that environment. These typically
|
|
||||||
are read into the environment with a language-specific
|
|
||||||
[dotenv](https://github.com/bkeepers/dotenv) tool.
|
|
||||||
|
|
||||||
Mise supports this convention. In addition to specifying individual non-secret
|
|
||||||
env vars, you can also instruct `mise` to read-in a `.env` file like so:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[env]
|
|
||||||
PORT=3344
|
|
||||||
_.file = ".env"
|
|
||||||
```
|
|
||||||
|
|
||||||
The `_.file` line tells `mise` that there is a file `.env` with key-value pairs
|
|
||||||
that it should read in. It can even handle `.env.json` and `.env.toml` file
|
|
||||||
formats.
|
|
||||||
|
|
||||||
To ensure that `mise` is picking up the values from the `.env` file, you can
|
|
||||||
run the following command and make sure they show up in the output:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ mise env
|
|
||||||
```
|
|
||||||
|
|
||||||
[source](https://mise.jdx.dev/environments/secrets.html)
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Run A Command With Specific Tool Version
|
|
||||||
|
|
||||||
Because I'm using `mise` to manage the versions of tools like Node, I can
|
|
||||||
execute commands in the context of specific versions. Behind the scenes `mise`
|
|
||||||
makes sure I have the necessary tool(s) installed at the desired version(s).
|
|
||||||
|
|
||||||
So, [`mise exec` command](https://mise.jdx.dev/cli/exec.html) will default to
|
|
||||||
using the latest version of a tool if I haven't been more specific. At the time
|
|
||||||
of this writing, for Node, that is v23.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ mise exec node -- node --version
|
|
||||||
v23.9.0
|
|
||||||
```
|
|
||||||
|
|
||||||
To be specific I could specify the major version with `node@23` like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mise exec node@23 -- npx repomix
|
|
||||||
Need to install the following packages:
|
|
||||||
repomix@0.2.39
|
|
||||||
Ok to proceed? (y) y
|
|
||||||
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Or if I wanted to use a different, older version of Node, I could specify that
|
|
||||||
as well. We can see it will first install that and then execute the command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ mise exec node@22 -- npx repomix
|
|
||||||
gpg: Signature made Tue Feb 11 04:44:53 2025 CST
|
|
||||||
gpg: using RSA key C0D6248439F1D5604AAFFB4021D900FFDB233756
|
|
||||||
gpg: Good signature from "Antoine du Hamel <duhamelantoine1995@gmail.com>" [unknown]
|
|
||||||
|
|
||||||
📦 Repomix v0.2.39
|
|
||||||
|
|
||||||
...
|
|
||||||
```
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user