Posts tagged: cli

All posts with the tag "cli"

100 posts latest post 2026-06-29
Publishing rhythm
Jun 2026 | 2 posts

Moving panes between tmux sessions is something that makes tmux a very flexible and powerful tool. I don’t need this feature very often, but it comes in clutch when you need it.

Pull a pane from any other session #

Using choose-window I was able to come up with a way to select any pane withing any other session and join it into my current session.

# Choose a pane to join in horizontally
bind f choose-window -Z 'join-pane -h -s "%%"'

Push/Pull from scratch #

I’ve long had this one in my tmux config, I always have a “scratch” session that I’m running, I often use for looking at things like k9s accross repos within a popup.

This use case puts a pane into the scratch session, then pulls it back out. I will use this to move a pane between sessions in the rare cases I need to do this.

# push the active pane into the scratch session horizonally
bind -n M-f join-pane -ht scratch
# pull the last active pane from the scratch session horizonally into this session
bind -n M-F join-pane -hs scratch

jq has some syntax that will sneak up on you with complexity. It looks so good, and so understandable, but everytime I go to use it myself, I don’t get it. ijq is an interactive alternative to jq that gives you and nice repl that you can iterate on queries quickly.

paru -Syu ijq

Here are some other articles, I decided to link at the time of writing this article.

JUT | Read Notebooks in the Terminal

Comprehensive guide to creating kedro nodes

Kedro - My Data Is Not A Table

I am often editing my own scripts as I develop them. I want to make a better workflow for working with scripts like this.

Currently #

Currently I am combining nvim with a which subshell to etit these files like this.

for now lets use my todo command as an example

nvim `which todo`

First pass #

On first pass I made a bash function to do exactly what I have been doing.

ewhich () {$EDITOR `which "$1"`}

The $1 will pass the first input to the which subshell. Now we can edit our todo script like this.

ewich todo

Note, I use bash functions instead of aliases for things that require input.

Final State #

This works fine for commands that are files, but not aliases or shell functions. Next I jumped to looking at the output of command -V $1.

  • if the command is not found, search for a file
  • if its a builtin, exit
  • if its an alias, open my ~/.alias file to that line
  • if its a function, open my ~/.alias file to that line
ewhich () {
case `command -V $1` in
    "$1 not found")
        FILE=`fzf --prompt "$1 not found searching ..." --query $1`
        [ -z "$FILE" ] && echo "closing" || $EDITOR $FILE;;
    *"is a shell builtin"*)
        echo "$1 is a builtin";;
    *"is an alias"*)
        $EDITOR ~/.alias +/alias\ $1;;
    *"is a shell function"*)
        $EDITOR ~/.alias +/^$1;;
    *)
        $EDITOR `which "$1"`;;
esac

a bit more ergo, and less readable #

To make it easier to type, at the sacrifice of readability for anyone watching I added a single character e alias to ewhich. So when I want to edit anything I just use e.

alias e=ewhich

Results #

Here is a quick screencast of how it works.

GitPython is a python api for your git repos, it can be quite handy when you need to work with git from python.

Use Case #

I recently made myself a handy tool for making screenshots in python and it need to do a git commit and push from within the script. For this I reached for GitPython.

How I Quickly Capture Screenshots directly into My Blog

Installation #

GitPython is a python library hosted on pypi that we will want to install into our virtual environments using pip.

pip install GitPython

Create a Repo Object #

Import Repo from the git library and create an instance of the Repo object by giving it a path to the directory containing your .git directory.

from git import Repo
repo = Repo('~/git/waylonwalker.com/')

Two interfaces #

from the docs

It provides abstractions of git objects for easy access of repository data, and additionally allows you to access the git repository more directly using either a pure python implementation, or the faster, but more resource intensive git command implementation.

I only needed to use the more intensive but familar to me git command implementation to get me project off the ground. There is a good tutorial to get you started with their pure python implementation in their docs.

Status #

Requesting the git status can be done as follows.

note I have prefixed my commands with »> to distinguish between the command I entered and the output.

>>> print(repo.git.status())

On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        blog/

You can even pass in flags that you would pass into the cli.

>>> print(repo.git.status("-s"))

<!--markata-attribution-->
?? blog/

log #

Example of using the log.

print(repo.git.log('--oneline', '--graph'))

* 0d28bd8 fix broken image link
* 3573928 wip screenshot-to-blog
* fed9abc wip screenshot-to-blog
* d383780 update for wsl2
* ad72b14 wip screenshot-to-blog
* 144c2f3 gratitude-180

Find Deleted Files #

We can even do things like find all files that have been deleted and the hash they were deleted.

print(repo.git.log('--diff-filter', 'D', '--name-only', '--pretty=format:"%h"'))

git find deleted files

full post on finding deleted files

My Experience #

This library seemed pretty straightforward and predicatable once I realized there were two main implementations and that I would already be familar with the more intensive git command implementation.

From the same Author that brought us command line essentials like fd and bat written in rust comes pastel an incredible command-line tool to generate, analyze, convert and manipulate colors.

Install #

You can install from one of the releases, follow the instructions for your system from the repo. I chose to go the nix route. I have enjoyed the simplicity of the nix package manager being cross platform and have very up to date packages in it.

nix-env --install pastel

Mixing colors #

Something I often do to blend colors together is add a little alpha to something over top of a background. I can simulate this by mixing colors.

pastel color cornflowerblue | pastel mix goldenrod -f .1

Here is one from the docs that show how you can generate a color palette from random colors, mix in some red, lighten and format all in one pipe.

pastel random | pastel mix red | pastel lighten 0.2 | pastel format hex

color picker #

I am on Ubuntu 20.10 as I write this and it works flawlessly. When I call the command, a color picker gui pops up along with an rgb panel. I can pick from the panel or from anywhere on my screen.

pastel color-picker

pastel pick

Conversions #

I often will want to convert a color from rgb to hex or hsl vice versa. I open google and search. This is one part that I could really use adding to my workflow.

Check it #

Here I can mix up a dark grey with rgb, mix in 20% cornflowerblue, and grab the hex value.

pastel color 50,50,50 | pastel mix cornflowerblue -f .2
my terminal output from mixing grey

I really want to get this into my workflow. I saw it quite awhile ago but have not done much color work. Lately I have been doing a bit more front end, and have been getting into game development. This is the time to stop googling random color mixers and use this one.

This morning I was trying to install a modpack on my minecraft server after getting a zip file, and its quite painful when I unzip everything in the current directory rather than the directory it belongs in.

I had the files on a Windows Machine #

So I’ve been struggling to get mods installed on linux lately and the easiest way to download the entire pack rather than each mod one by one seems to be to use the overwolf application on windows. Once I have the modpack I can start myself a small mod-server by zipping it, putting it in a mod-server directory and running a python http.server

python -m http.server

Downoading on the server #

Then I go back to my server and download the modpack with wget.

wget 10.0.0.171:8000/One%2BBlock%2BServer%2BPack-1.4.zip

Unzip to the minecraft-data directory #

Now I can unzip my mods into the minecraft-data directory.

unzip One+Block+Server+Pack-1.4.zip -d minecraft-data

Running the server with docker #

I run the minecraft server with docker, which is setup to mount the minecraft-data directory.

Running a Minecraft Server in Docker

A bit more on that in the other post, but when I download the whole modpack like this I make these changes to my docker compose. (commented out lines)

version: "3.8"

services:
  mc:
    container_name: walkercraft
    image: itzg/minecraft-server:java8
    environment:
      EULA: "TRUE"
      TYPE: "FORGE"
      VERSION: 1.15.2
      # MODS_FILE: /extras/mods.txt
      # REMOVE_OLD_MODS: "true"
    tty: true
    stdin_open: true
    restart: unless-stopped
    ports:
      - 25565:25565
    volumes:
      - ./minecraft-data:/data
      # - ./mods.txt:/extras/mods.txt:ro

volumes:
  data:

I recently gave a talk at python web conf 2022, and one of the things I did when I should have been working on my presentation was workig on how my presentation looked… classic procrastination technique.

Slide One #

Lets use this section to show what it looks like as I change my styles.

from markata import Markata
Markata()
markata.run()

☝ This is how my website is built

  • write markdown
  • build site
  • publish

default #

This is what the above slide looks like in lookatme.

default styles

Set focus to the most important element #

The way I write my slides I want the most prominant element to be the slides title, not the presentation title. The slides title is generally the point I am trying to make, I will leave some supporting information if I want, but sometimes, I just have a title.

styles:
    title:
        bg: default
        fg: '#e1af66'
    headings:
        '1':
            bg: default
            fg: '#ff66c4,bold,italics'
            prefix: ' ⇁ '
            suffix: ' ↽ '
set the focus on the slide title styles

by default he prefix/suffix was a full block that just went transparant into the slide. I thought the harpoons were fun and went with them on a whim

The box characters bother me #

The box characters are fine really, but it really bothers me that they are not conneted. The author is probably doing this because it looks ok on most systems, and many terminals dont have their fonts right and wont align anyways. I am not sure if I ever had a windows terminal other than their new Terminal that properly connected box characters.

    quote:
        side: '│'
        style:
            bg: default
            fg: '#aaa'
        top_corner: '╭'
        bottom_corner: '╰'

Add Author #

Adding author to the root of the frontmatter of the document will add it to the bottom left of the slides.

author: '@_waylonwalker'
lookatme slides with author defined

Style the author #

We can style the foreground and background of this text by adding something like this to the styles section of the frontmatter.

author:
    bg: default
    fg: '#368ce2'

While we are at it, lets style the rest of the footer to my own theme. Let’s pop this into the style and see what it looks like.

date:
    bg: default
    fg: '#368ce2'
slides:
    bg: default
    fg: '#368ce2'
lookatme slides with author styled

reduce the padding #

When I am presenting I am punched in as big as I can go, and which makes the padding massive. I want as much as the screen real estate devoted to making big readable text as I can.

padding:
    bottom: 0
    left: 0
    right: 0
    top: 0
lookatme slides with no more padding

final results #

Here is what the final frontmatter looks like to fully style my talk.

---
date: 2022-03-24
templateKey: til
title: Style Lookatme Slides a bit more Personal
tags:
  - python
  - cli
  - python
author: '@_waylonwalker'
styles:
    padding:
        bottom: 0
        left: 0
        right: 0
        top: 0
    title:
        bg: default
        fg: '#e1af66'
    date:
        bg: default
        fg: '#368ce2'
    slides:
        bg: default
        fg: '#368ce2'
    headings:
        '1':
            bg: default
            fg: '#ff66c4,bold,italics'
            prefix: ' ⇁ '
            suffix: ' ↽ '
    quote:
        side: '│'
        style:
            bg: default
            fg: '#aaa'
        top_corner: '╭'
        bottom_corner: '╰'
    author:
        bg: default
        fg: '#368ce2'
---

I love the freedom of writing in markdown. It allows me to write content from the comfort of my editor with very little focus on page style. It turns out that markdown is also a fantastic tool for creating slides.

Present from the terminal #

I will most often just present right from the terminal using lookatme. Presenting from the terminal lets me see the results quick right from where I am editing. It also allows me to pop into other terminal applications quickly.

reveal.js #

I sometimes also use reveal.js, but that’s for another post. It is handy that it lives in the browser and is easier to share.

New Slides #

I leverage auto slides when I write my slides in markdown. The largest heading, usually an h2 for me, becomes the new slide marker. Otherwise my process is not much different, It just becomes a shorter writing style.

Installation #

lookatme is a python library that is available on pypi, you can install it with the pip command.

python -m pip install lookatme

Since it’s a command line application it works great with pipx. This prevents the need to manage virtual environments yourself or ending up with packages clashing in your system python environment.

pipx install lookatme

From my terminal #

lookatme {filepath}

I just run it with pipx.

pipx run \
 --spec git+https://github.com/waylonwalker/lookatme \
 lookatme {filepath} \
 --live-reload \
 --style gruvbox-dark

Note, I use a custom fork of lookatme. It’s schema validation did not like the date format of my blog posts, so I have a one line fix built into my fork that is pretty specific to me.

From Neovim #

most often what I do

From Neovim I use a plugin I created for sending out commands to tmux called telegraph. This sends the above command to a new session that I can bounce between quickly.

nnoremap <leader><leader>s <cmd>lua require'telegraph'.telegraph({cmd='pipx run --spec git+https://github.com/waylonwalker/lookatme lookatme {filepath} --live-reload --style gruvbox-dark', how='tmux'})<CR>

Setting up your git pager to your liking can help you navigate diffs and logs much more efficiently. You can set it to whatever pager you like so that your keys feel nice and smooth and your fingers know exactly what to do. You might even gain a few extra features.

Setting the pager #

You can set the pager right from your command line with the following command.

git config --global core.pager 'more'

You can also set your pager by editing your global .gitconfig file which by default is set to ~/.gitconfig.

[core]
    pager = more

Color #

In my experience you need to turn colors off with nvim. bat handles them and looks good either way, but nvim will be plain white and display the color codes as plain text if color is on.

git config --global color.pager no

Pagers I have tried #

Here are some various configs that I tried. For some reason line numbers in bat really bothered me, but when in nvim they felt ok. I am going to try running both of them for a few days and see which I like better. I think having some of my nvim config could be really handy for things like yanking a commit hash to the system clipboard without touching the mouse.

# bat
git config --global core.pager 'bat'

# nvim in read only mode
git config --global core.pager 'nvim -R'

# turn colors off
git config --global color.pager no

# bat with no line numbers
git config --global core.pager 'bat --style=plain'

# nvim with no line numbers and a specific rc file
git config --global core.pager "nvim -R +'set nonumber norelativenumber' -u ~/.config/nvim/init-git.vim"

reset back to the default #

If you are afraid to try one of these settings, don’t be you can always change it back. If you tried one and dont like it just --unset the config that you just tried.

git config --global --unset core.pager
git config --global --unset color.pager

The other option you have is to open your .gitconfig file and delete the lines of config that set your pager.

If you have ever mistyped a git command very close to an existing one you have likely seen this message.

❯ git chekout dev
git: 'chekout' is not a git command. See 'git --help'.

The most similar command is
        checkout

Automatically run the right one #

What you might not have known is that you can configure git to just run this command for you.

# Gives you 0.1 seconds to respond
git config --global help.autocorrect 1

# Gives you 1 seconds to respond
git config --global help.autocorrect 10

# Gives you 5 seconds to respond
git config --global help.autocorrect 50

Fat Fingers Gone #

Now when you typo a git command it will autmatically run after the configured number of tenths of a second.

❯ git chkout get-error
WARNING: You called a Git command named 'chkout', which does not exist.
Continuing in 1.0 seconds, assuming that you meant 'checkout'.
M       pages/blog/how-i-deploy-2021.md
M       pages/hot_tips/001.md
M       pages/templates/gratitude_card.html
M       plugins/index.py
M       plugins/publish_amp.py
M       plugins/render_template_variables.py
M       plugins/youtube.py
M       requirements.txt
M       static/index.html
Switched to branch 'get-error'

My config #

I’m rocking 10 for now just to see how I feel about it, but honestly I cannot think of a time that I have seen the original warning that was not what I wanted. This at least gives me some time to respond if I am unsure.

git config --global help.autocorrect 10

yq is a command line utility for parsing and querying yaml, like jq does for json.

This is for me #

I love that all of these modern tools built in go and rust, just give you a zipped up executable right from GitHub releases, but it’s not necessarily straight forward how to install them. yq does one of the best jobs I have seen, giving you instructions on how to get a specific version and install it.

I use a bunch of these tools, and for what its worth I trust the devs behind them to make sure they don’t break. This so far has worked out well for me, but if it ever doesn’t I can always pick an older version.

Just give me the latest #

Since I am all trusting of them I just want the latest version. I do not want to update a shell script with new versions, or even care about what then next version is, I just want it. Luckily you can script the release page for the latest version on all that I have came accross.

What is the latest #

I wrote or stole, I think I wrote it, this line of bash quite awhile ago, and it has served me well for finding the latest release for any GitHub project using releases. Just update it with the name of the tool, org, and repo and it works.

YQ_VERSION=$(curl --silent https://github.com/mikefarah/yq/releases/latest | tr -d '"' | sed 's/^.*tag\///g' | sed 's/>.*$//g' | sed 's/^v//')

Install with your shell #

Now that we know how to consistently get the right version, I generally right click the release in the releases page, replace the version with ${TOOL_VERSION} and put it in this wget call, then move the binary over to ~/.local/bin

local tmp=`mktemp -dt install-XXXXXX`
pushd $tmp
YQ_VERSION=$(curl --silent https://github.com/mikefarah/yq/releases/latest | tr -d '"' | sed 's/^.*tag\///g' | sed 's/>.*$//g' | sed 's/^v//')
wget https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64.tar.gz -O- -q | tar -zxf - -C /tmp
cp yq_linux_amd64 ~/.local/bin/yq
popd

Install with ansible #

Now I don’t want to worry about missing yq again, so I am added it to my ansible install script. This way it’s installed everyt time I setup a new system with all of my favorite cli’s.

- name: check is yq installed
  shell: command -v yq
  register: yq_exists
  ignore_errors: yes
  tags:
    - yq

- name: Install yq
  when: yq_exists is failed
  shell: |
    local tmp=`mktemp -dt install-XXXXXX`
    pushd $tmp
    YQ_VERSION=$(curl --silent https://github.com/mikefarah/yq/releases/latest | tr -d '"' | sed 's/^.*tag\///g' | sed 's/>.*$//g' | sed 's/^v//')
    wget https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64.tar.gz -O- -q | tar -zxf - -C /tmp
    cp yq_linux_amd64 {{ lookup('env', 'HOME') }}/.local/bin/yq
    popd
  tags:
    - yq

This is how I installed it, of course you can always follow Mike’s instructions from the repo.

Kedro rich is a very new and unstable (it’s good, just not ready) plugin for kedro to make the command line prettier.

Install kedro rich #

There is no pypi package yet, but it’s on github. You can pip install it with the git url.

pip install git+https://github.com/datajoely/kedro-rich

Kedro run #

You can run your pipeline just as you normally would, except you get progress bars and pretty prints.

kedro run
kedro rich pretty run

Kedro catalog #

Listing out catalog entries from the command line now print out a nice pretty table.

kedro catalog list
kedro rich catalog list table output

Give it a star #

Go to the GitHub repo and give it a star, Joel deserves it.

So worktrees, I always thought they were a big scary things. Turns out they are much simpler than I thought.

Myth #1 #

no special setup

I thought you had to be all in or worktrees or normal git, but not both. When I see folks go all in on worktrees they start with a bare repo, while its true this is the way you go all in, its not true that this is required.

Lets make a worktree #

Making a worktree is as easy as making a branch. It’s actually just a branch that lives in another place in your filesystem.

# checkout a new worktree called compare based on main in /tmp/project
git worktree add -b compare /tmp/project main

# checkout a new worktree called compare based on HEAD in /tmp/project
git worktree add -b compare /tmp/project

# checkout a worktree from an existing feature branch in /tmp/project
git worktree add /tmp/project my-existing-feature-branch

The worktree that you create is considered a linked worktree, while the original worktree is called the main worktree

Note that I put this in my tmp directory because I don’t expect it to live very long, my recent use case was to compare two files after a big formatting change. You put these where you want, but dont come at me when your /tmp gets wiped and you loose work.

Myth #2 #

they are hidden mysterious creatures

Just like branches git has some nice commands to help us understand what worktrees we have on our system. Firstly we have something very specific to worktrees to list them out.

git worktree list

gives the output

/home/u_walkews/git/git-work-play  b202442 [main]
/tmp/another                       d9b2cf1 [another]

Even the branch command gives a bit different output for a worktree.

git branch

gives this output, notice the + denotes an actively linked worktree, and the * gives the active branch. If you cd over to the worktree directory, these will switch roles.

+ another
  just-a-branch
* main

You can only checkout a branch in one place #

If you try to checkout a branch that is checked out in a linked worktree, you will be presented with an error, and it will not let you check out a second copy of that branch.

❯ git checkout another
fatal: 'another' is already checked out at '/tmp/another'

Myth #3 #

once you go worktree, you worktree

Once you have worktrees on your system, you have a few ways to get rid of them. Using git’s way feels much superior, but if your a doof like me and didn’t read the manual before you rm /tmp/another -rf you will notice that the worktree is still active. If you run git worktree prune it will clean that right up.

git worktree remove another

rm /tmp/another
git worktree prune

It won’t let you remove if you have changes #

This makes me think that remove is a much safer option. If you have uncommitted changes, git worktree remove will throw an error, and make you commit or use --force to remove the worktree.

❯ git worktree remove another
fatal: 'another' contains modified or untracked files, use --force to delete it

RTFM #

read the friendly manual

There is a ton more information in the man page for worktrees, these are just the parts that seemed really useful to me out of the gate.

man git worktree

I recently found a really great plugin by mhinz to open files in neovim from a different tmux split, without touching neovim at all.

Installation #

neovim-remote is not a neovim plugin at all, it’s a python cli that you can install with pip. Unlike the repo suggests, I use pipx to install nvr.

pipx install neovim-remote

How I use it #

I have this added to my .envrc that is in every one of my projects. This will tie a neovim session to that directory, and all directories under it.

export NVIM_LISTEN_ADDRESS=/tmp/nvim-$(basename $PWD)

In my workflow I open a tmux session for each project, so this essentially ties a neovim session to a tmux session.

Open neovim #

First open neovim, but with the nvr command. This will open neovim, and look pretty much the same as always.

nvr

If you try to run nvr again in another shell nothing will happen as its already runnin under that address, but if you give it a filename it will open the file in the first instance of neovim that you opened.

nvr readme.md

If you have ever ran which <command> and see duplicate entries it’s likely that you have duplicate entries in your $PATH. You can clean this up with a one liner at the end of your bashrc or zshrc.

eval "typeset -U path"

There is GNU coreutils command called mktemp that is super handy in shell scripts to make temporary landing spots for files so that they never clash with another instance, and will automatically get cleaned up when you restart, or whenever /tmp gets wiped. I’m not sure when that is, but I don’t expect it to be long.

Making temp directories #

Here are some examples of making temp directories in different places, my favorite is mktemp -dt mytemp-XXXXXX.

# makes a temporary directory in /tmp/ with the defaul template tmp.XXXXXXXXXX
mktemp

# makes a temporary directory in your current directory
mktemp --directory mytemp-XXXXXX
# shorter version
mktemp -d mytemp-XXXXXX

# same thing, but makes a file
mktemp mytemp-XXXXXX

# makes a temporary directory in your /tmp/ directory (or what ever you have configured as your TMPDIR)
mktemp --directory --tmpdir mytemp-XXXXXX
# shorter version
mktemp -dt mytemp-XXXXXX

# same thing, but makes a file
mktemp --tmpdir mytemp-XXXXXX
# shorter version
mktemp -t mytemp-XXXXXX

Use Case #

Here is a sample script that shows how to capture the tempdir as a variable and reuse it. Here is an example of curling my bootstrap file into a temp directory and running it from that directory.

local tmp=`mktemp -dt bootstrap-XXXXXX`
pushd $tmp
curl https://raw.githubusercontent.com/WaylonWalker/devtainer/main/bootstrap > bootstrap
bash bootstrap
popd

Templates #

You must have at least 3 trailing X’s that mktemp will replace with random characters. I played with it for a bit, it kinda allows for some trailing characters, and will not fill groups of X’s earlier in your template, only the last consecutive string.

My randomm samples I played with.

waylonwalker.com on  main [!?]  v3.9.7 (waylonwalker.com) took 2m24s
❯ mktemp myXtemp-XaXbXXXX -dt
/tmp/myXtemp-XaXbx9hn

waylonwalker.com on  main [!?]  v3.9.7 (waylonwalker.com)
❯ mktemp myXtemp-XaXbXXXXs -dt
/tmp/myXtemp-XaXb2tpGs

waylonwalker.com on  main [!?]  v3.9.7 (waylonwalker.com)
❯ mktemp myXtemp-XaXbXXcXXs -dt
mktemp: too few X's in template ‘myXtemp-XaXbXXcXXs’

waylonwalker.com on  main [!?]  v3.9.7 (waylonwalker.com)
❯ mktemp myXtemp-XaXbXXcXXs -dt

waylonwalker.com on  main [!?]  v3.9.7 (waylonwalker.com)
❯ mktemp myXtemp-XaXbXXXXt -dt
/tmp/myXtemp-XaXbe8PWt

waylonwalker.com on  main [!?]  v3.9.7 (waylonwalker.com)
❯ mktemp myXtemp-XXX-you-XXX -dt
/tmp/myXtemp-XXX-you-48l

waylonwalker.com on  main [!?]  v3.9.7 (waylonwalker.com)
❯ mktemp myXtemp-XXX-you-XX -dt
mktemp: too few X's in template ‘myXtemp-XXX-you-XX’

RTFM #

The man page has good stuff on all the flags that you might need.

man mktemp

Once you give a branch the big D (git branch -D mybranch) its gone, its lost from your history. It’s completely removed from your log. There will be no reference to these commits, or will there?

TLDR #

Checkout is your savior, all you need is the commit hash.

Immediate Regret #

your terminal is still open

We have all done this, you give branch the big D only to realize it was the wrong one. Don’t worry, not all is lost, this is the easiest to recover from. When you run the delete command you will see something like this.

❯ git branch -D new
Deleted branch new (was bc02a64).

Notice the hash is right there is the hash of your commit. You can use that to get your content back.

git checkout -b bc02a64
git branch new

# or in one swoop checkout your new branch at the `start-point` you want
git checkout -b new bc02a64

Delayed reaction #

you have closed your terminal

If you have closed your terminal, or have deleted with a gui or something that does not tell you the hash as you run it, don’t fret, all your work is still there (as long as you have commited). You just have to dig it out. The reflog contains a list of all git operations that have occurred on your git repo, and can be incredibly helpful with this.

Kinda Recent #

If your botched delete operation was recent just diving right into the reflog will show it.

❯ git reflog
03a3338 (main) HEAD@{0}: checkout: moving from new to main
bc02a64 (HEAD -> another, new) HEAD@{4}: commit: newfile
03a3338 (main) HEAD@{2}: checkout: moving from main to new

In this example, I checked out a branch called new, commited a new file, then switched back to main and deleted new.

Now That I have the commit hash I can use the same solution to get my branch back.

git checkout -b bc02a64
git branch new

# or in one swoop checkout your new branch at the `start-point` you want
git checkout -b new bc02a64

A lot has happened since then #

If a lot has happened since then, you are going to need to pull out some more tool to sift through that reflog, especially if its a big one. The first suggestion that I have is to pipe into grep and look for commit messages, or the name of the branch.

❯ git reflog | grep "moving from"
03a3338 HEAD@{1}: checkout: moving from main to branch/oops
03a3338 HEAD@{2}: checkout: moving from oops to main
03a3338 HEAD@{4}: checkout: moving from main to oops
03a3338 HEAD@{5}: checkout: moving from another to main
bc02a64 HEAD@{6}: checkout: moving from main to another
03a3338 HEAD@{7}: checkout: moving from another to main
bc02a64 HEAD@{8}: checkout: moving from new to another
bc02a64 HEAD@{9}: checkout: moving from bc02a64bbe5683d905e333e8dfcbbb91a5e77549 to new
bc02a64 HEAD@{10}: checkout: moving from main to bc02a64bbe56
03a3338 HEAD@{11}: checkout: moving from new to main
03a3338 HEAD@{13}: checkout: moving from main to new
03a3338 HEAD@{14}: checkout: moving from other to main
03a3338 HEAD@{18}: checkout: moving from main to other

git has a built in --grep flag, but I don’t think there is a way to filter by branch name, regardless it still is helpful.

❯ git reflog --grep new
bc02a64 (HEAD -> another, new) HEAD@{4}: commit: newfile

Maybe if you can remember a filename you can pass in -- <filename>.

git reflog -- readme.md

RTFM #

There are many other ways to slice up a git log, and reflog alike. check out man git log for some more flags.

As I am toying around with textual, I am wanting some popup user input to take over. Textual is still pretty new and likely to change quite significantly, so I don’t want to overdo the work I put into it, So for now on my personal tuis I am going to shell out to tmux.

The Problem #

The main issue is that when you are in a textual app, it kinda owns the input. So if you try to run another python function that calls for input it just cant get there. There is a textual-inputs library that covers this, and it might work really well for some use cases, but many of my use cases have been for things that are pre-built like copier, and I am trying to throw something together quick.

textual is still very beta

Part of this comes down to the fact that textual is still very beta and likely to change a lot, so all of the work I have done with it is for quick and dirty, or fun side projects.

The Solution #

So the solution that was easiest for me… shell out to a tmux popup. The application I am working on wants to create new documents using copier templates. copier has a fantastic cli that walks throught he template variables and asks the user to fill them in, so I just shell out to that with Popen. Make sure that you wait for this process to finish otherwise there will be bit of jank in your textual app.

async def action_new_post(self) -> None:
    proc = subprocess.Popen(
        'tmux popup "copier copy plugins/todo-template tasks"', shell=True
    )
    proc.wait()

example #

Here is what the running todo application looks like with the copier popup over it.

example of the popup running over textual

tmux popups

a bit more on tmux-popus [here] https://waylonwalker.com/tmux-popups/)

Git has a built in way to rebase all the way back to the beginning of time. There is no need to scroll through the log to find the first hash, or find the total number of commits. Just use --root.

git rebase --root

Git reflog can perform some serious magic in reviving your hard work from the dead if you happen to loose it.

caveat #

You must git commit! If you never commit the file, git cannot help you. You might look into your trashcan, filesystem versions, onedrive, box, dropbox. If you have none of this, then you are probably hosed.

practice #

I really like to practice these techniques before I need to use them so that I understand how they work in a low stakes fashion. This helps me understand what I can and cannot do, and how to do it in a place that does not matter in any way at all.

This is what I did to revive a dropped docker-compose.yml file. The idea is that if I can find the commit hash, I can cherry-pick it.

git init
touch readme.md
git add readme.md
git commit -m "add readme"
touch docker-compose.yml
git add docker-compose.yml
git commit -m "add docker-compose"
git reset 3cfc --hard
git reflog
# copy the hash of the commit with my docker-compose commit
git cherry-pick fd74df3

reflog #

Here was the final reflog that shows all of my git actions. note I did reset twice.

❯ git reflog --name-only
0404b6a (HEAD -> main) HEAD@{0}: cherry-pick: add docker-compose
docker-compose.yml
3cfcab9 HEAD@{1}: reset: moving to 3cfc
readme.md
9175695 HEAD@{2}: cherry-pick: add docker-compose
docker-compose.yml
3cfcab9 HEAD@{3}: reset: moving to 3cfc
readme.md
fd74df3 HEAD@{4}: commit: add docker-compose
docker-compose.yml
3cfcab9 HEAD@{5}: reset: moving to HEAD
readme.md
3cfcab9 HEAD@{6}: commit (initial): add readme
readme.md