diff --git a/README.md b/README.md index d5c3b99f4..af6e15f1c 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ A simple terminal UI for git commands
-[![GitHub Releases](https://img.shields.io/github/downloads/jesseduffield/lazygit/total)](https://github.com/jesseduffield/lazygit/releases) [![Go Report Card](https://goreportcard.com/badge/github.com/jesseduffield/lazygit)](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [![GolangCI](https://golangci.com/badges/github.com/jesseduffield/lazygit.svg)](https://golangci.com) [![GitHub tag](https://img.shields.io/github/tag/jesseduffield/lazygit.svg)](https://github.com/jesseduffield/lazygit/releases/latest) [![homebrew](https://img.shields.io/homebrew/v/lazygit)](https://github.com/Homebrew/homebrew-core/blob/master/Formula/lazygit.rb) +[![GitHub Releases](https://img.shields.io/github/downloads/jesseduffield/lazygit/total)](https://github.com/jesseduffield/lazygit/releases) [![Go Report Card](https://goreportcard.com/badge/github.com/jesseduffield/lazygit)](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [![GolangCI](https://golangci.com/badges/github.com/jesseduffield/lazygit.svg)](https://golangci.com) [![GitHub tag](https://img.shields.io/github/tag/jesseduffield/lazygit.svg)](https://github.com/jesseduffield/lazygit/releases/latest) [![homebrew](https://img.shields.io/homebrew/v/lazygit)](https://github.com/Homebrew/homebrew-core/blob/master/Formula/lazygit.rb) -![demo-compressed](https://github.com/jesseduffield/lazygit/assets/8456633/7e77d895-05a9-4571-a55b-240ac80f75eb) +![commit_and_push](../assets/demo/commit_and_push-compressed.gif) @@ -316,25 +316,29 @@ See the [docs](docs/Custom_Command_Keybindings.md) ### Git Bisect -![demo-compressed](https://github.com/jesseduffield/lazygit/assets/8456633/b2752a77-3bd0-4b65-8e36-e643ae70c396) +![bisect](../assets/demo/bisect-compressed.gif) ### Cherry-pick -![cherry_pick-compressed](https://github.com/jesseduffield/lazygit/assets/8456633/2d1205c8-9980-40b0-8b6a-221497099715) +![cherry_pick](../assets/demo/cherry_pick-compressed.gif) ### Interactive Rebase -![demo-compressed](https://github.com/jesseduffield/lazygit/assets/8456633/04d6dbe4-845a-41de-8072-0e475765ec63) +![interactive_rebase](../assets/demo/interactive_rebase-compressed.gif) ### Nuking the working tree For when you really want to just get rid of anything that shows up when you run `git status` (and yes that includes dirty submodules) [kidpix style](https://www.youtube.com/watch?v=Ur7_A4JusMU) -![nuke_working_tree-compressed](https://github.com/jesseduffield/lazygit/assets/8456633/06facd9c-166c-4ff5-b011-12e584ff8163) +![Nuke working tree](../assets/demo/nuke_working_tree-compressed.gif) ### Amend old commit -![amend_old_commit-compressed](https://github.com/jesseduffield/lazygit/assets/8456633/de7010c0-6902-4fad-8fe5-7197956ca40d) +![amend_old_commit](../assets/demo/amend_old_commit-compressed.gif) + +### Stage individual lines + +![stage_lines](../assets/demo/stage_lines-compressed.gif) ## Contributing diff --git a/demo/record_demo.sh b/demo/record_demo.sh index 0ef379377..97d5c2f36 100755 --- a/demo/record_demo.sh +++ b/demo/record_demo.sh @@ -1,15 +1,44 @@ #!/bin/sh -TEST=$1 - set -e -if [ -z "$TEST" ] +TYPE=$1 +TEST=$2 + +usage() { + echo "Usage: $0 [gif|mp4] " + echo "e.g. using full path: $0 gif pkg/integration/tests/demo/nuke_working_tree.go" + exit 1 +} + +if [ "$#" -ne 2 ] then - echo "Usage: $0 " + usage +fi + +if [ "$TYPE" != "gif" ] && [ "$TYPE" != "mp4" ] +then + usage exit 1 fi +if [ -z "$TEST" ] +then + usage +fi + +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 + +OUTPUT_DIR="$WORKTREE_PATH/demo" + if ! command -v terminalizer &> /dev/null then echo "terminalizer could not be found" @@ -24,18 +53,29 @@ then exit 1 fi -# get last part of the test path and set that as the output name +# Get last part of the test path and set that as the output name # example test path: pkg/integration/tests/01_basic_test.go # For that we want: NAME=01_basic_test NAME=$(echo "$TEST" | sed -e 's/.*\///' | sed -e 's/\..*//') +# Add the demo to the tests list (if missing) so that it can be run go generate pkg/integration/tests/tests.go -mkdir -p demo/output +mkdir -p "$OUTPUT_DIR" -terminalizer -c demo/config.yml record --skip-sharing -d "go run cmd/integration_test/main.go cli --slow $TEST" "demo/output/$NAME" -terminalizer render "demo/output/$NAME" -o "demo/output/$NAME.gif" -COMPRESSED_PATH="demo/output/$NAME-compressed.gif" -gifsicle --colors 256 --use-col=web -O3 < "demo/output/$NAME.gif" > "$COMPRESSED_PATH" +# 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" + +# Then we convert it to either an mp4 or gif based on the command line argument +if [ "$TYPE" = "mp4" ] +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" +fi echo "Demo recorded to $COMPRESSED_PATH" diff --git a/docs/dev/Demo_Recordings.md b/docs/dev/Demo_Recordings.md index 11924c199..1068e688f 100644 --- a/docs/dev/Demo_Recordings.md +++ b/docs/dev/Demo_Recordings.md @@ -12,6 +12,8 @@ Ideally we'd run this whole thing through docker but we haven't got that working npm i -g terminalizer # for gif compression npm i -g gifsicle +# for mp4 conversion +brew install ffmpeg # font with icons wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.tar.xz && \ @@ -37,16 +39,44 @@ You can use the same flow as we use with integration tests when you're writing a It's good to add captions explaining what task if being performed. Use the existing demos as a guide. +### Setting up the assets worktree + +We store assets (which includes demo recordings) in the `assets` branch, which is a branch that shares no history with the main branch and exists purely for storing assets. Storing them separately means we don't clog up the code branches with large binaries. + +The scripts and demo definitions live in the code branches but the output lives in the assets branch so to be able to create a video from a demo you'll need to create a linked worktree for the assets branch which you can do with: + +```sh +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) + ### Recording the demo Once you're happy with your demo you can record it using: ```sh -scripts/record_demo.sh +scripts/record_demo.sh [gif|mp4] # e.g. -scripts/record_demo.sh pkg/integration/tests/demo/interactive_rebase.go +scripts/record_demo.sh gif pkg/integration/tests/demo/interactive_rebase.go ``` -### Storing demos +~~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).~~ -This part is subject to change. I'm thinking of storing all gifs in the `assets` branch. But yet to finalize on that. -For now, feel free to upload `demo/demo-compressed.gif` to GitHub by dragging and dropping it in a file in the browser (e.g. the README). +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. + +### Including demos in README/docs + +If you've followed the above steps you'll end up with your output in your assets worktree. + +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 +![Nuke working tree](../assets/demo/interactive_rebase-compressed.gif) +``` + +This means we can update assets without needing to update the docs that embed them. diff --git a/pkg/integration/components/confirmation_driver.go b/pkg/integration/components/confirmation_driver.go index aad5cc248..7934b351c 100644 --- a/pkg/integration/components/confirmation_driver.go +++ b/pkg/integration/components/confirmation_driver.go @@ -40,6 +40,12 @@ func (self *ConfirmationDriver) Cancel() { self.getViewDriver().PressEscape() } +func (self *ConfirmationDriver) Wait(milliseconds int) *ConfirmationDriver { + self.getViewDriver().Wait(milliseconds) + + return self +} + func (self *ConfirmationDriver) checkNecessaryChecksCompleted() { if !self.hasCheckedContent || !self.hasCheckedTitle { self.t.Fail("You must both check the content and title of a confirmation popup by calling Title()/Content() before calling Confirm()/Cancel().") diff --git a/pkg/integration/tests/demo/amend_old_commit.go b/pkg/integration/tests/demo/amend_old_commit.go new file mode 100644 index 000000000..b77a62bd1 --- /dev/null +++ b/pkg/integration/tests/demo/amend_old_commit.go @@ -0,0 +1,62 @@ +package demo + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var AmendOldCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Amend old commit", + ExtraCmdArgs: []string{}, + Skip: false, + IsDemo: true, + SetupConfig: func(config *config.AppConfig) { + // No idea why I had to use version 2: it should be using my own computer's + // font and the one iterm uses is version 3. + config.UserConfig.Gui.NerdFontsVersion = "2" + config.UserConfig.Gui.ShowFileTree = false + }, + SetupRepo: func(shell *Shell) { + shell.CreateNCommitsWithRandomMessages(60) + shell.NewBranch("feature/demo") + + shell.CloneIntoRemote("origin") + + shell.SetBranchUpstream("feature/demo", "origin/feature/demo") + + shell.UpdateFile("navigation/site_navigation.go", "package navigation\n\nfunc Navigate() {\n\tpanic(\"unimplemented\")\n}") + shell.CreateFile("docs/README.md", "my readme content") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.SetCaptionPrefix("Amend an old commit") + t.Wait(1000) + + t.Views().Files(). + IsFocused(). + SelectedLine(Contains("site_navigation.go")). + PressPrimaryAction() + + t.Views().Commits(). + Focus(). + NavigateToLine(Contains("Improve accessibility of site navigation")). + Wait(500). + Press(keys.Commits.AmendToCommit). + Tap(func() { + t.ExpectPopup().Confirmation(). + Title(Equals("Amend commit")). + Wait(1000). + Content(AnyString()). + Confirm() + + t.Wait(1000) + }). + Press(keys.Universal.Push). + Tap(func() { + t.ExpectPopup().Confirmation(). + Title(Equals("Force push")). + Content(AnyString()). + Wait(1000). + Confirm() + }) + }, +}) diff --git a/pkg/integration/tests/demo/filter.go b/pkg/integration/tests/demo/filter.go new file mode 100644 index 000000000..2e62ba444 --- /dev/null +++ b/pkg/integration/tests/demo/filter.go @@ -0,0 +1,93 @@ +package demo + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var Filter = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Filter branches", + ExtraCmdArgs: []string{}, + Skip: false, + IsDemo: true, + SetupConfig: func(config *config.AppConfig) { + // No idea why I had to use version 2: it should be using my own computer's + // font and the one iterm uses is version 3. + config.UserConfig.Gui.NerdFontsVersion = "2" + }, + SetupRepo: func(shell *Shell) { + shell.CreateNCommitsWithRandomMessages(30) + shell.NewBranch("feature/user-authentication") + shell.NewBranch("feature/payment-processing") + shell.NewBranch("feature/search-functionality") + shell.NewBranch("feature/mobile-responsive") + shell.NewBranch("bugfix/fix-login-issue") + shell.NewBranch("bugfix/fix-crash-bug") + shell.NewBranch("bugfix/fix-validation-error") + shell.NewBranch("refactor/improve-performance") + shell.NewBranch("refactor/code-cleanup") + shell.NewBranch("refactor/extract-method") + shell.NewBranch("docs/update-readme") + shell.NewBranch("docs/add-user-guide") + shell.NewBranch("docs/api-documentation") + shell.NewBranch("experiment/new-feature-idea") + shell.NewBranch("experiment/try-new-library") + shell.NewBranch("chore/update-dependencies") + shell.NewBranch("chore/add-test-cases") + shell.NewBranch("chore/migrate-database") + shell.NewBranch("hotfix/critical-bug") + shell.NewBranch("hotfix/security-patch") + shell.NewBranch("feature/social-media-integration") + shell.NewBranch("feature/email-notifications") + shell.NewBranch("feature/admin-panel") + shell.NewBranch("feature/analytics-dashboard") + shell.NewBranch("bugfix/fix-registration-flow") + shell.NewBranch("bugfix/fix-payment-bug") + shell.NewBranch("refactor/improve-error-handling") + shell.NewBranch("refactor/optimize-database-queries") + shell.NewBranch("docs/improve-tutorials") + shell.NewBranch("docs/add-faq-section") + shell.NewBranch("experiment/try-alternative-algorithm") + shell.NewBranch("experiment/implement-design-concept") + shell.NewBranch("chore/update-documentation") + shell.NewBranch("chore/improve-test-coverage") + shell.NewBranch("chore/cleanup-codebase") + shell.NewBranch("hotfix/critical-security-vulnerability") + shell.NewBranch("hotfix/fix-production-issue") + shell.NewBranch("feature/integrate-third-party-api") + shell.NewBranch("feature/image-upload-functionality") + shell.NewBranch("feature/localization-support") + shell.NewBranch("feature/chat-feature") + shell.NewBranch("bugfix/fix-broken-link") + shell.NewBranch("bugfix/fix-css-styling") + shell.NewBranch("refactor/improve-logging") + shell.NewBranch("refactor/extract-reusable-component") + shell.NewBranch("docs/add-changelog") + shell.NewBranch("docs/update-api-reference") + shell.NewBranch("experiment/implement-new-design") + shell.NewBranch("experiment/try-different-architecture") + shell.NewBranch("chore/clean-up-git-history") + shell.NewBranch("chore/update-environment-configuration") + shell.CreateFileAndAdd("env_config.rb", "EnvConfig.call(false)\n") + shell.Commit("Update env config") + shell.CreateFileAndAdd("env_config.rb", "# Turns out we need to pass true for this to work\nEnvConfig.call(true)\n") + shell.Commit("Fix env config issue") + shell.Checkout("docs/add-faq-section") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.SetCaptionPrefix("Fuzzy filter branches") + t.Wait(1000) + + t.Views().Branches(). + Focus(). + Wait(500). + Press(keys.Universal.StartSearch). + Tap(func() { + t.Wait(500) + + t.ExpectSearch().Type("environ").Confirm() + }). + Wait(500). + PressEnter() + }, +}) diff --git a/pkg/integration/tests/demo/stage_lines.go b/pkg/integration/tests/demo/stage_lines.go new file mode 100644 index 000000000..6bfa21a98 --- /dev/null +++ b/pkg/integration/tests/demo/stage_lines.go @@ -0,0 +1,85 @@ +package demo + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var originalFile = `# Lazygit + +Simple terminal UI for git commands + +![demo](https://user-images.gh.com/demo.gif) + +## Installation + +### Homebrew + +` + +var updatedFile = `# Lazygit + +Simple terminal UI for git +(Not too simple though) + +![demo](https://user-images.gh.com/demo.gif) + +## Installation + +### Homebrew + +Just do brew install lazygit and bada bing bada +boom you have begun on the path of laziness. + +` + +var StageLines = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Stage individual lines", + ExtraCmdArgs: []string{}, + Skip: false, + IsDemo: true, + SetupConfig: func(config *config.AppConfig) { + // No idea why I had to use version 2: it should be using my own computer's + // font and the one iterm uses is version 3. + config.UserConfig.Gui.NerdFontsVersion = "2" + config.UserConfig.Gui.ShowFileTree = false + config.UserConfig.Gui.ShowCommandLog = false + }, + SetupRepo: func(shell *Shell) { + shell.NewBranch("docs-fix") + shell.CreateNCommitsWithRandomMessages(30) + shell.CreateFileAndAdd("docs/README.md", originalFile) + shell.Commit("Update docs/README") + shell.UpdateFile("docs/README.md", updatedFile) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.SetCaptionPrefix("Stage individual lines") + t.Wait(1000) + + t.Views().Files(). + IsFocused(). + PressEnter() + + t.Views().Staging(). + IsFocused(). + Press(keys.Main.ToggleDragSelect). + PressFast(keys.Universal.NextItem). + PressFast(keys.Universal.NextItem). + Wait(500). + PressPrimaryAction(). + Wait(500). + PressEscape() + + t.Views().Files(). + IsFocused(). + Press(keys.Files.CommitChanges). + Tap(func() { + t.ExpectPopup().CommitMessagePanel(). + Type("Update tagline"). + Confirm() + }) + + t.Views().Commits(). + Focus() + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 27cb3513e..75508784e 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -89,11 +89,14 @@ var tests = []*components.IntegrationTest{ custom_commands.OmitFromHistory, custom_commands.SuggestionsCommand, custom_commands.SuggestionsPreset, + demo.AmendOldCommit, demo.Bisect, demo.CherryPick, demo.CommitAndPush, + demo.Filter, demo.InteractiveRebase, demo.NukeWorkingTree, + demo.StageLines, diff.Diff, diff.DiffAndApplyPatch, diff.DiffCommits,