mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-28 18:26:03 -04:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3889653d1b | ||
|
|
b4e70420cc | ||
|
|
7072461d8a | ||
|
|
8c1852e074 | ||
|
|
84dd4b41d7 | ||
|
|
2fe4905817 | ||
|
|
bcde5e68cb | ||
|
|
1027c1cd92 | ||
|
|
efdf8b360c | ||
|
|
2c74c8d6d3 |
@@ -68,6 +68,10 @@ bump-lazycore:
|
||||
record-demo:
|
||||
demo/record_demo.sh $(filter-out $@,$(MAKECMDGOALS))
|
||||
|
||||
.PHONY: rerecord-demos
|
||||
rerecord-demos:
|
||||
demo/rerecord_demos.sh $(filter-out $@,$(MAKECMDGOALS))
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
go mod tidy && go mod vendor
|
||||
|
||||
@@ -47,7 +47,8 @@ A simple terminal UI for git commands
|
||||
|
||||
[](https://github.com/jesseduffield/lazygit/releases) [](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [](https://app.codacy.com/gh/jesseduffield/lazygit/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [](https://app.codacy.com/gh/jesseduffield/lazygit/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage) [](https://golangci-lint.run/) [](https://github.com/jesseduffield/lazygit/releases/latest) [](https://formulae.brew.sh/formula/lazygit)
|
||||
|
||||

|
||||
<!-- demo: commit_and_push -->
|
||||
<video src="https://github.com/user-attachments/assets/a1f28c26-7545-4ba8-ae79-b37f848b1b8e" controls></video>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
# Specify a command to be executed
|
||||
# like `/bin/bash -l`, `ls`, or any other commands
|
||||
# the default is bash for Linux
|
||||
# or powershell.exe for Windows
|
||||
command: echo "YOU NEED TO SPECIFY YOUR OWN COMMAND WITH THE -d ARG"
|
||||
|
||||
# Specify the current working directory path
|
||||
# the default is the current working directory path
|
||||
cwd: null
|
||||
|
||||
# Export additional ENV variables
|
||||
env:
|
||||
recording: true
|
||||
|
||||
# Explicitly set the number of columns
|
||||
# or use `auto` to take the current
|
||||
# number of columns of your shell
|
||||
cols: 120 # 100
|
||||
|
||||
# Explicitly set the number of rows
|
||||
# or use `auto` to take the current
|
||||
# number of rows of your shell
|
||||
rows: 35 # 30
|
||||
|
||||
# Amount of times to repeat GIF
|
||||
# If value is -1, play once
|
||||
# If value is 0, loop indefinitely
|
||||
# If value is a positive number, loop n times
|
||||
repeat: 0
|
||||
|
||||
# Quality
|
||||
# 1 - 100
|
||||
# Higher quality seems to make no difference, but running it through
|
||||
# gifsicle ends up with a much better compressed version.
|
||||
quality: 100
|
||||
|
||||
# Delay between frames in ms
|
||||
# If the value is `auto` use the actual recording delays
|
||||
frameDelay: auto
|
||||
|
||||
# Maximum delay between frames in ms
|
||||
# Ignored if the `frameDelay` isn't set to `auto`
|
||||
# Set to `auto` to prevent limiting the max idle time
|
||||
maxIdleTime: 2000
|
||||
|
||||
# The surrounding frame box
|
||||
# The `type` can be null, window, floating, or solid`
|
||||
# To hide the title use the value null
|
||||
# Don't forget to add a backgroundColor style with a null as type
|
||||
frameBox:
|
||||
type: floating
|
||||
title: Lazygit
|
||||
style:
|
||||
border: 0px black solid
|
||||
backgroundColor: "#1d1d1d"
|
||||
margin: -5px
|
||||
|
||||
# Add a watermark image to the rendered gif
|
||||
# You need to specify an absolute path for
|
||||
# the image on your machine or a URL, and you can also
|
||||
# add your own CSS styles
|
||||
watermark:
|
||||
imagePath: null
|
||||
style:
|
||||
position: absolute
|
||||
right: 15px
|
||||
bottom: 15px
|
||||
width: 100px
|
||||
opacity: 0.9
|
||||
|
||||
# Cursor style can be one of
|
||||
# `block`, `underline`, or `bar`
|
||||
cursorStyle: block
|
||||
|
||||
# Font family
|
||||
# You can use any font that is installed on your machine
|
||||
# in CSS-like syntax
|
||||
# Download from:
|
||||
# https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.zip
|
||||
# Not using the mono font because it makes icons too small.
|
||||
fontFamily: "DejaVuSansM Nerd Font"
|
||||
|
||||
# The size of the font
|
||||
fontSize: 8
|
||||
|
||||
# The height of lines
|
||||
lineHeight: 1
|
||||
|
||||
# The spacing between letters
|
||||
letterSpacing: 0
|
||||
|
||||
# Theme
|
||||
theme:
|
||||
background: "transparent"
|
||||
foreground: "#dddad6"
|
||||
cursor: "#c7c7c7"
|
||||
black: "#7a7a7a"
|
||||
red: "#fc4384"
|
||||
green: "#b3e33b"
|
||||
yellow: "#ffa727"
|
||||
blue: "#102895"
|
||||
magenta: "#c930c7"
|
||||
cyan: "#00c5c7"
|
||||
white: "#c7c7c7"
|
||||
brightBlack: "#676767"
|
||||
brightRed: "#ff7fac"
|
||||
brightGreen: "#c8ed71"
|
||||
brightYellow: "#ebdf86"
|
||||
brightBlue: "#6871ff"
|
||||
brightMagenta: "#ff76ff"
|
||||
brightCyan: "#5ffdff"
|
||||
brightWhite: "#fffefe"
|
||||
+172
-46
@@ -2,55 +2,72 @@
|
||||
|
||||
set -e
|
||||
|
||||
TYPE=$1
|
||||
TEST=$2
|
||||
# The repository the demo is uploaded to. GitHub only plays videos that live in
|
||||
# its own attachment store, and an attachment is tied to one repository.
|
||||
REPO=jesseduffield/lazygit
|
||||
|
||||
# The issue that collects the demo recordings. Posting a comment there is what
|
||||
# makes an uploaded video readable by people who are not signed in to GitHub.
|
||||
# It can stay closed; commenting on a closed issue publishes the video just as
|
||||
# well, and does not reopen it.
|
||||
PUBLISH_ISSUE=6051
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [gif|mp4] <test path>"
|
||||
echo "e.g. using full path: $0 gif pkg/integration/tests/demo/nuke_working_tree.go"
|
||||
echo "Usage: $0 [--no-upload] <test path>"
|
||||
echo "e.g. $0 pkg/integration/tests/demo/nuke_working_tree.go"
|
||||
echo
|
||||
echo "--no-upload leaves the video in demo/output and stops there, for"
|
||||
echo "checking how a change to demo/settings.tape turns out."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$#" -ne 2 ]
|
||||
UPLOAD=true
|
||||
|
||||
if [ "$1" = "--no-upload" ]
|
||||
then
|
||||
UPLOAD=false
|
||||
shift
|
||||
fi
|
||||
|
||||
TEST=$1
|
||||
|
||||
if [ "$#" -ne 1 ]
|
||||
then
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ "$TYPE" != "gif" ] && [ "$TYPE" != "mp4" ]
|
||||
TOOLS="vhs ttyd ffmpeg"
|
||||
|
||||
if [ "$UPLOAD" = true ]
|
||||
then
|
||||
usage
|
||||
exit 1
|
||||
TOOLS="$TOOLS gh"
|
||||
fi
|
||||
|
||||
if [ -z "$TEST" ]
|
||||
for TOOL in $TOOLS
|
||||
do
|
||||
if ! command -v "$TOOL" > /dev/null 2>&1
|
||||
then
|
||||
echo "$TOOL could not be found"
|
||||
echo "Install it with: brew install $TOOL"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$UPLOAD" = true ]
|
||||
then
|
||||
usage
|
||||
fi
|
||||
WORKTREE_PATH=$(git worktree list | grep assets | awk '{print $1}')
|
||||
|
||||
WORKTREE_PATH=$(git worktree list | grep assets | awk '{print $1}')
|
||||
if [ -z "$WORKTREE_PATH" ]
|
||||
then
|
||||
echo "Could not find assets worktree. You'll need to create a worktree for the assets branch using the following command:"
|
||||
echo "git worktree add .worktrees/assets assets"
|
||||
echo "The assets branch has no shared history with the main branch: it exists to store assets which are too large to store in the main branch."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$WORKTREE_PATH" ]
|
||||
then
|
||||
echo "Could not find assets worktree. You'll need to create a worktree for the assets branch using the following command:"
|
||||
echo "git worktree add .worktrees/assets assets"
|
||||
echo "The assets branch has no shared history with the main branch: it exists to store assets which are too large to store in the main branch."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUTPUT_DIR="$WORKTREE_PATH/demo"
|
||||
|
||||
if ! command -v terminalizer &> /dev/null
|
||||
then
|
||||
echo "terminalizer could not be found"
|
||||
echo "Install it with: npm install -g terminalizer"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v "gifsicle" &> /dev/null
|
||||
then
|
||||
echo "gifsicle could not be found"
|
||||
echo "Install it with: npm install -g gifsicle"
|
||||
exit 1
|
||||
OUTPUT_DIR="$WORKTREE_PATH/demo"
|
||||
else
|
||||
OUTPUT_DIR=demo/output
|
||||
fi
|
||||
|
||||
# Get last part of the test path and set that as the output name
|
||||
@@ -63,19 +80,128 @@ go generate pkg/integration/tests/tests.go
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# First we record the demo into a yaml representation
|
||||
terminalizer -c demo/config.yml record --skip-sharing -d "go run cmd/integration_test/main.go cli --slow $TEST" "$OUTPUT_DIR/$NAME"
|
||||
# Then we render it into a gif
|
||||
terminalizer render "$OUTPUT_DIR/$NAME" -o "$OUTPUT_DIR/$NAME.gif"
|
||||
SCRATCH=$(mktemp -d)
|
||||
trap 'rm -rf "$SCRATCH"' EXIT
|
||||
|
||||
# Then we convert it to either an mp4 or gif based on the command line argument
|
||||
if [ "$TYPE" = "mp4" ]
|
||||
TAPE="$SCRATCH/$NAME.tape"
|
||||
RECORDING="$SCRATCH/$NAME.mp4"
|
||||
OUTPUT="$OUTPUT_DIR/$NAME.mp4"
|
||||
|
||||
# The two quotes in the marker keep the literal VHSDONE out of the command line
|
||||
# that stays on screen while we wait for the marker to be printed.
|
||||
cat > "$TAPE" <<EOF
|
||||
Output "$RECORDING"
|
||||
|
||||
Source demo/settings.tape
|
||||
|
||||
# The command is typed while the recording is hidden, so there is nothing to
|
||||
# gain from animating it.
|
||||
Set TypingSpeed 0ms
|
||||
|
||||
Hide
|
||||
Type "go run cmd/integration_test/main.go cli --slow $TEST; echo VHS''DONE"
|
||||
Enter
|
||||
Wait+Screen@180s /Local branches/
|
||||
Show
|
||||
Wait+Screen@600s /VHSDONE/
|
||||
Hide
|
||||
EOF
|
||||
|
||||
vhs "$TAPE"
|
||||
|
||||
if [ ! -f "$RECORDING" ]
|
||||
then
|
||||
COMPRESSED_PATH="$OUTPUT_DIR/$NAME.mp4"
|
||||
ffmpeg -y -i "$OUTPUT_DIR/$NAME.gif" -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" "$COMPRESSED_PATH"
|
||||
else
|
||||
COMPRESSED_PATH="$OUTPUT_DIR/$NAME-compressed.gif"
|
||||
gifsicle --colors 256 --use-col=web -O3 < "$OUTPUT_DIR/$NAME.gif" > "$COMPRESSED_PATH"
|
||||
echo "vhs recorded the demo but wrote no video."
|
||||
echo "vhs 0.12.0 does this; see https://github.com/charmbracelet/vhs/issues/787."
|
||||
echo "Install a working version with: go install github.com/charmbracelet/vhs@v0.11.0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Demo recorded to $COMPRESSED_PATH"
|
||||
# The browser draws its playback controls over the bottom of the video, and
|
||||
# they are tall enough to hide lazygit's caption line. Pad the frame so that
|
||||
# the caption sits above them. Chrome draws the tallest bar of the three, and
|
||||
# at the width a README gives the video its buttons start to overlap the
|
||||
# caption below about 90px, so there is not much room to trim here. Measure it
|
||||
# again if demo/settings.tape changes the size of the recording.
|
||||
CAPTION_CLEARANCE=90
|
||||
|
||||
BACKGROUND=$(sed -n 's/.*"background": *"\(#[0-9a-fA-F]*\)".*/\1/p' demo/settings.tape)
|
||||
|
||||
if [ -z "$BACKGROUND" ]
|
||||
then
|
||||
echo "Could not read the background colour from demo/settings.tape"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Hold the last frame for a moment so that the end state stays readable, and
|
||||
# move the moov atom to the front so that the video starts playing before it
|
||||
# has fully downloaded.
|
||||
ffmpeg -y -loglevel error -i "$RECORDING" \
|
||||
-vf "tpad=stop_mode=clone:stop_duration=1.2,pad=iw:ih+$CAPTION_CLEARANCE:0:0:color=$BACKGROUND" \
|
||||
-c:v libx264 -crf 23 -preset slow -pix_fmt yuv420p \
|
||||
-movflags +faststart -an "$OUTPUT"
|
||||
|
||||
if [ "$UPLOAD" = false ]
|
||||
then
|
||||
echo "Demo recorded to $OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# GitHub's web editor posts to this endpoint when you drag a file into a
|
||||
# comment box. It is undocumented, but it accepts an ordinary token, so we can
|
||||
# upload from here. You need push access to $REPO for it to work. If the
|
||||
# endpoint ever goes away, drag the video into a comment box on github.com
|
||||
# instead and copy the URL that GitHub inserts.
|
||||
REPOSITORY_ID=$(gh api "repos/$REPO" --jq .id)
|
||||
|
||||
RESPONSE=$(curl --silent --show-error --fail \
|
||||
--request POST \
|
||||
--header "Authorization: Bearer $(gh auth token)" \
|
||||
--header "Accept: application/json" \
|
||||
--header "Content-Type: video/mp4" \
|
||||
--data-binary "@$OUTPUT" \
|
||||
"https://uploads.github.com/user-attachments/assets?name=$NAME.mp4&content_type=video%2Fmp4&repository_id=$REPOSITORY_ID")
|
||||
|
||||
URL=$(echo "$RESPONSE" | sed -e 's/.*"url":"//' -e 's/".*//')
|
||||
|
||||
if [ -z "$URL" ]
|
||||
then
|
||||
echo "Could not read an attachment URL out of GitHub's response:"
|
||||
echo "$RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# An attachment stays private until a posted comment somewhere in the
|
||||
# repository refers to it. Until that happens the video is a 404 for anyone who
|
||||
# is not signed in, and the README shows a broken player. Referring to it once
|
||||
# makes it public for good, even if the comment is deleted afterwards, so we
|
||||
# collect the recordings in one issue and leave the comments in place.
|
||||
gh api "repos/$REPO/issues/$PUBLISH_ISSUE/comments" \
|
||||
--raw-field "body=$NAME
|
||||
|
||||
$URL" > /dev/null
|
||||
|
||||
# Make sure that worked before handing over a URL, because the person recording
|
||||
# the demo is signed in and will not see the failure.
|
||||
ATTEMPT=0
|
||||
while [ "$ATTEMPT" -lt 30 ]
|
||||
do
|
||||
if curl --silent --fail --output /dev/null --max-time 20 --range 0-1 "$URL"
|
||||
then
|
||||
break
|
||||
fi
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$ATTEMPT" -eq 30 ]
|
||||
then
|
||||
echo "$URL is still not readable without signing in to GitHub."
|
||||
echo "Embedding it now would give logged-out readers a broken player."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Demo recorded to $OUTPUT"
|
||||
echo
|
||||
echo "Embed it with:"
|
||||
echo "<video src=\"$URL\" controls></video>"
|
||||
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Re-records the demos that a page embeds and points the page at the new
|
||||
# videos. Use it when a change to demo/settings.tape, or to lazygit's
|
||||
# appearance, leaves the existing recordings looking out of date.
|
||||
#
|
||||
# A page names the demo behind each video in a comment above it:
|
||||
#
|
||||
# <!-- demo: commit_and_push -->
|
||||
# <video src="https://github.com/user-attachments/assets/..." controls></video>
|
||||
#
|
||||
# GitHub drops that comment when it renders the page, so it costs the reader
|
||||
# nothing. This script re-records the demo the comment names and rewrites the
|
||||
# URL on the line below it.
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [--no-upload] [page ...]"
|
||||
echo "e.g. $0 README.md"
|
||||
echo
|
||||
echo "Re-records every demo the given pages embed. With no page given,"
|
||||
echo "that means README.md."
|
||||
echo
|
||||
echo "--no-upload leaves the videos in demo/output and the pages untouched,"
|
||||
echo "which is what you want for reviewing a change to demo/settings.tape."
|
||||
exit 1
|
||||
}
|
||||
|
||||
NO_UPLOAD=
|
||||
|
||||
if [ "$1" = "--no-upload" ]
|
||||
then
|
||||
NO_UPLOAD=--no-upload
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ "$1" = "-h" ] || [ "$1" = "--help" ]
|
||||
then
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ ! -x demo/record_demo.sh ]
|
||||
then
|
||||
echo "Run this from the root of the repository."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$#" -eq 0 ]
|
||||
then
|
||||
set -- README.md
|
||||
fi
|
||||
|
||||
for PAGE in "$@"
|
||||
do
|
||||
if [ ! -f "$PAGE" ]
|
||||
then
|
||||
echo "$PAGE does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NAMES=$(sed -n 's/^<!-- demo: \([A-Za-z0-9_]*\) -->$/\1/p' "$PAGE")
|
||||
|
||||
if [ -z "$NAMES" ]
|
||||
then
|
||||
echo "$PAGE embeds no demos. Each video needs a <!-- demo: <name> -->"
|
||||
echo "comment on the line above it to say which demo it came from."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for NAME in $NAMES
|
||||
do
|
||||
TEST="pkg/integration/tests/demo/$NAME.go"
|
||||
|
||||
if [ ! -f "$TEST" ]
|
||||
then
|
||||
echo "$PAGE asks for a demo called $NAME, but $TEST does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== $NAME ==="
|
||||
|
||||
if [ -n "$NO_UPLOAD" ]
|
||||
then
|
||||
demo/record_demo.sh --no-upload "$TEST"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Keep the recording chatter on screen, since a full run takes a while,
|
||||
# and read the new URL back out of it afterwards.
|
||||
LOG=$(mktemp)
|
||||
demo/record_demo.sh "$TEST" | tee "$LOG"
|
||||
URL=$(sed -n 's/.*<video src="\([^"]*\)".*/\1/p' "$LOG")
|
||||
rm -f "$LOG"
|
||||
|
||||
if [ -z "$URL" ]
|
||||
then
|
||||
echo "Recording $NAME produced no URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
awk -v marker="<!-- demo: $NAME -->" -v url="$URL" '
|
||||
hit { sub(/src="[^"]*"/, "src=\"" url "\""); hit = 0 }
|
||||
$0 == marker { hit = 1 }
|
||||
{ print }
|
||||
' "$PAGE" > "$PAGE.new"
|
||||
|
||||
mv "$PAGE.new" "$PAGE"
|
||||
|
||||
echo "$PAGE now points at $URL"
|
||||
done
|
||||
done
|
||||
@@ -0,0 +1,46 @@
|
||||
# Terminal settings shared by all demo recordings. The tape that
|
||||
# demo/record_demo.sh generates for a demo sources this file.
|
||||
|
||||
# Use the non-Mono Nerd Font variant; the Mono variant renders the icons too
|
||||
# small.
|
||||
Set FontFamily "SauceCodePro NF"
|
||||
Set FontSize 24
|
||||
Set LineHeight 1.0
|
||||
Set Padding 20
|
||||
|
||||
# vhs sizes the terminal in pixels rather than in cells, so these two numbers
|
||||
# are what produce a 120x35 grid at the font settings above. If you change the
|
||||
# font, the font size or the padding, record a tape that runs `stty size` and
|
||||
# adjust them until the grid is 120x35 again.
|
||||
Set Width 1866
|
||||
Set Height 1140
|
||||
|
||||
# There is no frame rate setting here because vhs ignores `Set Framerate` when
|
||||
# it writes a video; it captures at 25 fps either way.
|
||||
|
||||
# The frame around the focused view is bold green, and xterm.js draws bold text
|
||||
# in the bright variant of a colour, so brightGreen is the one those frames end
|
||||
# up using.
|
||||
|
||||
Set Theme {
|
||||
"background": "#1d1d1d",
|
||||
"foreground": "#dddad6",
|
||||
"cursor": "#c7c7c7",
|
||||
"selection": "#44475a",
|
||||
"black": "#7a7a7a",
|
||||
"red": "#fc4384",
|
||||
"green": "#3fb950",
|
||||
"yellow": "#ffa727",
|
||||
"blue": "#102895",
|
||||
"magenta": "#c930c7",
|
||||
"cyan": "#00c5c7",
|
||||
"white": "#c7c7c7",
|
||||
"brightBlack": "#676767",
|
||||
"brightRed": "#ff7fac",
|
||||
"brightGreen": "#56d364",
|
||||
"brightYellow": "#ebdf86",
|
||||
"brightBlue": "#6871ff",
|
||||
"brightMagenta": "#ff76ff",
|
||||
"brightCyan": "#5ffdff",
|
||||
"brightWhite": "#fffefe"
|
||||
}
|
||||
@@ -8,17 +8,18 @@ You'll want to familiarise yourself with how integration tests are written: see
|
||||
|
||||
Ideally we'd run this whole thing through docker but we haven't got that working. So you will need:
|
||||
```
|
||||
# for recording
|
||||
npm i -g terminalizer
|
||||
# for gif compression
|
||||
npm i -g gifsicle
|
||||
# for mp4 conversion
|
||||
brew install ffmpeg
|
||||
# for recording; vhs drives ttyd and ffmpeg under the hood
|
||||
brew install ttyd ffmpeg
|
||||
|
||||
# vhs 0.12.0 runs the tape, reports success and writes no video at all
|
||||
# (https://github.com/charmbracelet/vhs/issues/787), so pin the release
|
||||
# before it
|
||||
go install github.com/charmbracelet/vhs@v0.11.0
|
||||
|
||||
# font with icons
|
||||
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.tar.xz && \
|
||||
tar -xf DejaVuSansMono.tar.xz -C /usr/local/share/fonts && \
|
||||
rm DejaVuSansMono.tar.xz
|
||||
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/SourceCodePro.tar.xz && \
|
||||
tar -xf SourceCodePro.tar.xz -C ~/Library/Fonts && \
|
||||
rm SourceCodePro.tar.xz
|
||||
```
|
||||
|
||||
## Creating a demo
|
||||
@@ -49,34 +50,82 @@ The scripts and demo definitions live in the code branches but the output lives
|
||||
git worktree add .worktrees/assets assets
|
||||
```
|
||||
|
||||
Outputs will be stored in `.worktrees/assets/demos/`. We'll store three separate things:
|
||||
* the yaml of the recording
|
||||
* the original gif
|
||||
* either the compressed gif or the mp4 depending on the output you chose (see below)
|
||||
The mp4 of the recording will be stored in `.worktrees/assets/demo/`.
|
||||
|
||||
### Recording the demo
|
||||
|
||||
Once you're happy with your demo you can record it using:
|
||||
```sh
|
||||
scripts/record_demo.sh [gif|mp4] <path>
|
||||
scripts/record_demo.sh <path>
|
||||
# e.g.
|
||||
scripts/record_demo.sh gif pkg/integration/tests/demo/interactive_rebase.go
|
||||
scripts/record_demo.sh pkg/integration/tests/demo/interactive_rebase.go
|
||||
```
|
||||
|
||||
~~The gif format is for use in the first video of the readme (it has a larger size but has auto-play and looping)~~
|
||||
~~The mp4 format is for everything else (no looping, requires clicking, but smaller size).~~
|
||||
The terminal size, font and colours live in `demo/settings.tape`, which the
|
||||
script sources into the tape it generates for the demo.
|
||||
|
||||
Turns out that you can't store mp4s in a repo and link them from a README so we're gonna just use gifs across the board for now.
|
||||
While you are still working on how a demo looks, pass `--no-upload`. That
|
||||
leaves the video in `demo/output` (which is git-ignored) and stops there, so
|
||||
you can watch it without uploading anything or touching the assets worktree:
|
||||
|
||||
```sh
|
||||
scripts/record_demo.sh --no-upload pkg/integration/tests/demo/interactive_rebase.go
|
||||
```
|
||||
|
||||
### Including demos in README/docs
|
||||
|
||||
If you've followed the above steps you'll end up with your output in your assets worktree.
|
||||
Recording a demo does three things with the mp4: it writes it to your assets
|
||||
worktree, it uploads a copy to GitHub's attachment store, and it posts that
|
||||
copy as a comment on the issue named by `PUBLISH_ISSUE` in the script. Then it
|
||||
prints the tag to embed:
|
||||
|
||||
Within that worktree, stage all three output files and raise a PR against the assets branch.
|
||||
|
||||
Then back in the code branch, in the doc, you can embed the recording like so:
|
||||
```md
|
||||

|
||||
```html
|
||||
<video src="https://github.com/user-attachments/assets/<uuid>" controls></video>
|
||||
```
|
||||
|
||||
This means we can update assets without needing to update the docs that embed them.
|
||||
GitHub plays a video in a README only when it is served from its own attachment
|
||||
store. If you commit a video to the assets branch and link it the way we link
|
||||
the images, GitHub drops the whole `<video>` element when it renders the page.
|
||||
So the README reads the uploaded copy rather than the one in the assets
|
||||
worktree. Keep that one anyway, so that we still have the file if we ever need
|
||||
to upload it again. Stage it and raise a PR against the assets branch as you
|
||||
would for any other asset.
|
||||
|
||||
Attachment URLs are opaque and have no path we can predict, so a new recording
|
||||
of an existing demo means a new URL and an edit to the page that embeds it.
|
||||
|
||||
That comment on `PUBLISH_ISSUE` is not bookkeeping; it is what makes the video
|
||||
watchable. An uploaded attachment is readable only by people signed in to
|
||||
GitHub until some posted comment in the repository refers to it, and a README
|
||||
on a branch does not count. Skip that step and the video plays for you and
|
||||
404s for everyone else, which is easy to miss because you are signed in. The
|
||||
script waits until the video can be fetched without a token before it prints
|
||||
the tag. Referring to an attachment once is enough and cannot be undone, so
|
||||
the comments could be deleted later, but leaving them gives us a dated list of
|
||||
every recording.
|
||||
|
||||
Uploading needs push access to the lazygit repository. If you don't have it,
|
||||
record the demo, then ask a maintainer to upload the mp4 for you.
|
||||
|
||||
### Re-recording every demo on a page
|
||||
|
||||
Write the name of the demo above each video, so that we can find our way from
|
||||
a page back to the demo that produced it:
|
||||
|
||||
```html
|
||||
<!-- demo: commit_and_push -->
|
||||
<video src="https://github.com/user-attachments/assets/<uuid>" controls></video>
|
||||
```
|
||||
|
||||
GitHub drops the comment when it renders the page. With it in place, a change
|
||||
to `demo/settings.tape` or to lazygit's own appearance can be rolled out across
|
||||
every recording at once:
|
||||
|
||||
```sh
|
||||
scripts/rerecord_demos.sh
|
||||
# or, to look before you upload anything:
|
||||
scripts/rerecord_demos.sh --no-upload
|
||||
```
|
||||
|
||||
That re-records every demo the README embeds and rewrites each URL in place.
|
||||
Name other pages as arguments to do the same for them.
|
||||
|
||||
@@ -74,5 +74,9 @@ bump-gocui:
|
||||
demo *args:
|
||||
demo/record_demo.sh {{ args }}
|
||||
|
||||
# Re-record every demo the README embeds
|
||||
rerecord-demos *args:
|
||||
demo/rerecord_demos.sh {{ args }}
|
||||
|
||||
vendor:
|
||||
go mod tidy && go mod vendor
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
demo/rerecord_demos.sh "$@"
|
||||
@@ -8,8 +8,11 @@ gui:
|
||||
activeBorderColor:
|
||||
- green
|
||||
- bold
|
||||
# Not the "black" of the terminal palette: that is a real black in an
|
||||
# ordinary terminal, which makes the frames all but invisible when you run
|
||||
# a test with `just e2e-cli`.
|
||||
inactiveBorderColor:
|
||||
- black
|
||||
- '#999999'
|
||||
# Not important in tests but it creates clutter in demos
|
||||
showRandomTip: false
|
||||
animateExplosion: false # takes too long
|
||||
|
||||
Reference in New Issue
Block a user